Skip to content

Commit 5f32b62

Browse files
skill-temporal-developer-updater[bot]skill-sync[bot]donald-pinckneyclaude
authored
Implement planned topic: 0001-standalone-activities (#224)
* Add Python standalone activities reference * Add TypeScript standalone activities reference * Add .NET standalone activities reference * Add Java standalone activities reference * Finalize draft for 0001-standalone-activities * remove incomplete sections * Add core page which abstracts out all shared stuff * Standardize connection logic * Unify worker setup section across SDK standalone-activity refs Rename the worker section to "Worker setup & activity registration" in all four SDK files and lead with a single sentence noting the Activity is defined and registered exactly as normal. Drop the .NET "Define the Activity" section so no file repeats how to define an activity, matching the Python structure. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * re-organize to a LOGICAL structure, not just a flat list of H2 headings. * finish cleaning up parts other than calling activities * Get client connection in order * cleanup of operations content * Add links --------- Co-authored-by: skill-sync[bot] <skill-sync[bot]@users.noreply.github.com> Co-authored-by: Donald Pinckney <donald.pinckney@temporal.io> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a2f0903 commit 5f32b62

10 files changed

Lines changed: 760 additions & 0 deletions

File tree

SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ Check if `temporal` CLI is installed. If not, follow the instructions at `refere
7171
- Language-specific info at `references/{your_language}/gotchas.md`
7272
- **`references/core/versioning.md`** - Versioning strategies and concepts - how to safely change workflow code while workflows are running
7373
- Language-specific info at `references/{your_language}/versioning.md`
74+
- **`references/core/standalone-activities.md`** - Standalone Activities: run an Activity directly from a Client without a Workflow (Public Preview)
75+
- Language-specific info at `references/{your_language}/standalone-activities.md`
7476
- **`references/core/troubleshooting.md`** - Decision trees, recovery procedures
7577
- **`references/core/error-reference.md`** - Common error types, workflow status reference
7678
- **`references/core/interactive-workflows.md`** - Testing signals, updates, queries
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
> [!NOTE]
2+
> Standalone Activities are in Public Preview. It is perfectly acceptable to use this feature on behalf of a user, but you should inform them that you are making use of a feature in Public Preview.
3+
4+
# Standalone Activities (Concepts)
5+
6+
This document provides core conceptual explanations of Standalone Activities in Temporal. For language-specific implementation details, see `references/{your_language}/standalone-activities.md` for the language you are working in (Python, TypeScript, Java, .NET).
7+
8+
## What is a Standalone Activity?
9+
10+
A **Standalone Activity** is a top-level Activity Execution started directly by a Client, without using a Workflow. It is Temporal's job queue — the simplest way to run a single durable, retryable task.
11+
12+
The rule of thumb:
13+
14+
- **Need to orchestrate multiple Activities?** Use a Workflow.
15+
- **Just need to execute a single Activity?** Use a Standalone Activity.
16+
17+
The same Activity Function code runs in both modes with no changes — the only difference is how it is invoked. An Activity defined for a Workflow can also be executed standalone, and the Worker that hosts it does not need to know how it will be invoked.
18+
19+
Compared to wrapping a single Activity in a Workflow, a Standalone Activity:
20+
21+
- Reduces billable actions in Temporal Cloud.
22+
- Lowers latency for short-lived executions (fewer Worker round-trips).
23+
- Lives in a separate ID space from Workflows.
24+
25+
### Use cases
26+
27+
Standalone Activities fit durable single-job processing where you don't need multi-step orchestration:
28+
29+
- Sending an email
30+
- Processing a webhook
31+
- Syncing data
32+
- Any single-function task that benefits from built-in retries and timeouts
33+
34+
### Key features
35+
36+
- Execute Activities as a top-level primitive, without Workflow overhead.
37+
- Native async job lifecycle: **schedule → dispatch → process → result**.
38+
- Arbitrary-length jobs, with heartbeats for progress tracking.
39+
- **At-least-once execution by default**, with native retry policy and timeouts.
40+
- **At-most-once execution** when the retry policy's maximum attempts is 1.
41+
- Addressable by Activity ID / Run ID for result retrieval, cancellation, and termination.
42+
- Deduplication via configurable conflict policies.
43+
- Priority and fairness support.
44+
- Full visibility — list and count executions.
45+
46+
## Using Standalone Activities
47+
48+
### Defining activities
49+
50+
Defining standalone activities is IDENTICAL to defining activities callable from a workflow - there is no distinction AT ALL between the two at activity definition or worker configuration site. Follow language-specific guidance for how to normally define activities and configure workers to run them.
51+
52+
### Calling and Interacting with Standalone Activities
53+
54+
The CLI and every SDK exposes the same conceptual operations against a Standalone Activity (method names differ per language — see the language reference):
55+
56+
- **Execute** — durably enqueue the Activity, wait for a Worker to run it, and return the result.
57+
- **Start** — durably enqueue the Activity and return a handle immediately, without waiting.
58+
- **Get handle** — rebind a handle to a previously started Activity by ID (and optionally Run ID).
59+
- **Get result** — wait on a handle for completion. `execute` is equivalent to `start` followed by awaiting the handle's result.
60+
- **Cancel / Terminate** — via the handle or CLI.
61+
62+
**Choosing an Activity ID.** Every Standalone Activity call requires an **Activity ID**, which uniquely identifies that one call. It is the key you use later to get the result, describe, cancel, or terminate the Activity, and it is what conflict/reuse policies dedupe against. Use a **business-logic identifier** that uniquely identifies the call — for example `send-welcome-email:user-42`, `sync-invoice:INV-2026-001`, or `process-webhook:<event-id>`. This makes Activities addressable and naturally deduplicated by your domain. Only if you genuinely have no meaningful business-level identifier should you generate a **UUID** to use as the Activity ID.
63+
64+
Visibility operations are available as well:
65+
- **List** — enumerate Standalone Activity Executions matching a query. Only Standalone Activities are returned; Activities running inside Workflows are not included.
66+
- **Count** — return the total number of executions matching a query (running, completed, failed, etc. — not the number of queued tasks).
67+
- **Describe** — via the handle or CLI.
68+
69+
See below for a quick reference how to call these operations from the CLI rather than SDKs.
70+
71+
> [!IMPORTANT]
72+
> When using an SDK, these operations are owned by the Temporal Client, and belong **in your non-workflow application code**. It is INVALID to call an activity as a standalone activity from within a workflow: you instead should use standard within-workflow activity calls.
73+
74+
**Currently Supported SDKs: Python, TypeScript, Java, .NET**
75+
76+
## Quick CLI Standalone Activity Man Page
77+
78+
Ultimately, any standalone activity invocation code should live in your application code and use the appropriate SDK, but the Temporal CLI is a quick and easy way to test invoking standalone activities during development. All subcommands live under `temporal activity`.
79+
80+
The key operations are:
81+
82+
**Execute (start and wait for the result).** Blocks until the Activity completes and prints the result to stdout. Requires `--activity-id`, `--type`, `--task-queue`, and at least one of `--start-to-close-timeout` / `--schedule-to-close-timeout`:
83+
84+
```bash
85+
temporal activity execute \
86+
--activity-id my-activity-id \
87+
--type ComposeGreeting \
88+
--task-queue my-task-queue \
89+
--start-to-close-timeout 10s \
90+
--input '{"some-key": "some-value"}'
91+
```
92+
93+
`--input` takes a JSON value; pass it multiple times for multiple positional arguments. `--input-file` is also a convenient option for larger inputs. The same required flags apply to `start` below.
94+
95+
Reminder: `--activity-id` must be unique across all activity calls, as discussed above.
96+
97+
**Start (do not wait).** Enqueues the Activity and prints the Activity ID and Run ID without blocking:
98+
99+
```bash
100+
temporal activity start \
101+
--activity-id my-activity-id \
102+
--type ComposeGreeting \
103+
--task-queue my-task-queue \
104+
--start-to-close-timeout 10s \
105+
--input '{"some-key": "some-value"}'
106+
```
107+
108+
Outputs this JSON shape:
109+
110+
```json
111+
{
112+
"activityId": "my-activity-id",
113+
"runId": "019e84d3-949a-7a0e-ae78-63b8a0b172bd",
114+
"namespace": "default"
115+
}
116+
```
117+
118+
**Result (wait for a started Activity).** Waits for completion and prints the result. `--run-id` is optional and defaults to the latest run of that Activity ID:
119+
120+
```bash
121+
temporal activity result --activity-id my-activity-id
122+
```
123+
124+
**Describe (current state of one Activity).** Shows status, run state, task queue, timeouts, attempt count, etc.:
125+
126+
```bash
127+
temporal activity describe --activity-id my-activity-id
128+
```
129+
130+
**List / Count (visibility across many Activities).** Only Standalone Activity Executions are returned (Activities running inside Workflows are not):
131+
132+
```bash
133+
temporal activity list
134+
temporal activity count
135+
```
136+
137+
**Cancel / Terminate (stop an Activity).** `cancel` requests cooperative cancellation (surfaced to the Activity on its next heartbeat response); `terminate` forcefully ends it (Activity code cannot see or respond to it). Both accept `--reason`:
138+
139+
```bash
140+
temporal activity cancel --activity-id my-activity-id --reason "no longer needed"
141+
temporal activity terminate --activity-id my-activity-id --reason "no longer needed"
142+
```
143+
144+
## Observability
145+
146+
All existing Activity metrics apply to Standalone Activities (scheduled, started, completed, failed, timed out, canceled).
147+
148+
## Public Preview limitations
149+
150+
- Pause, reset, and update options are not supported (scheduled for GA).
151+
- The `TerminateExisting` conflict policy and `TerminateIfRunning` reuse policy are not yet supported.
152+
153+
## Temporal CLI support
154+
155+
- Requires **Temporal CLI v1.7.0+** and **Temporal Server v1.31.0+**. See `references/core/install_cli.md` if you need to update the CLI.
156+
- The Temporal Dev Server (`temporal server start-dev`) has Standalone Activities enabled by default.
157+
158+
## Temporal Cloud support
159+
160+
Standalone Activities are available in Temporal Cloud as a Public Preview feature. Because the SDK client config loaders read environment variables and TOML profiles, the same code runs against a local server or Temporal Cloud with no code changes.

references/dotnet/dotnet.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,5 @@ See `references/dotnet/testing.md` for info on writing tests.
199199
- **`references/dotnet/advanced-features.md`** — Schedules, worker tuning, dependency injection
200200
- **`references/dotnet/data-handling.md`** — Data converters, payload encryption, etc.
201201
- **`references/dotnet/versioning.md`** — Patching API, workflow type versioning, Worker Versioning
202+
- **`references/dotnet/standalone-activities.md`** — Standalone Activities: run an Activity directly from a Client without a Workflow (Public Preview). Concept overview at `references/core/standalone-activities.md`.
202203
- **`references/dotnet/determinism-protection.md`** — Runtime task detection, .NET Task determinism rules
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
> [!NOTE]
2+
> This feature is in Public Preview. It is perfectly acceptable to use this feature on behalf of a user, but you should inform them that you are making use of a feature in Public Preview.
3+
4+
## Overview
5+
6+
Standalone Activities are Activities run independently of any Workflow, started directly from a Temporal Client — useful when you need a single durable, retryable task (job-queue style) and not multi-step orchestration. The same Activity method can be executed both as a Standalone Activity and as a Workflow Activity with no code changes.
7+
8+
Standalone Activities are conceptually the same across all SDKs. Read the [cross-SDK concept file](references/core/standalone-activities.md) if you have not already, and then see below for the .NET SDK specific APIs for calling Standalone Activities.
9+
10+
## Prerequisites
11+
12+
- Temporal .NET SDK v1.12.0 or higher.
13+
- Temporal CLI v1.7.0 or higher — see [Temporal CLI install instructions](references/core/install_cli.md) if needed. Dev server includes Standalone Activities support.
14+
- For production, Temporal Server v1.31.0 or higher (or Temporal Cloud).
15+
16+
## Hosting Activities on a Worker
17+
18+
The Activity is defined just as activities normally are in Temporal. Worker registration is also the same.
19+
20+
```csharp
21+
using Microsoft.Extensions.Logging;
22+
using Temporalio.Client;
23+
using Temporalio.Common.EnvConfig;
24+
using Temporalio.Worker;
25+
using TemporalioSamples.StandaloneActivity;
26+
27+
var connectOptions = ClientEnvConfig.LoadClientConnectOptions();
28+
connectOptions.TargetHost ??= "localhost:7233";
29+
connectOptions.LoggerFactory = LoggerFactory.Create(builder =>
30+
builder.
31+
AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] ").
32+
SetMinimumLevel(LogLevel.Information));
33+
var client = await TemporalClient.ConnectAsync(connectOptions);
34+
35+
const string taskQueue = "standalone-activity-sample";
36+
37+
using var tokenSource = new CancellationTokenSource();
38+
Console.CancelKeyPress += (_, eventArgs) =>
39+
{
40+
tokenSource.Cancel();
41+
eventArgs.Cancel = true;
42+
};
43+
44+
using var worker = new TemporalWorker(
45+
client,
46+
new TemporalWorkerOptions(taskQueue).
47+
AddActivity(MyActivities.ComposeGreetingAsync)); // register whatever your activity(ies) is/are
48+
49+
await worker.ExecuteAsync(tokenSource.Token);
50+
```
51+
52+
## Calling and managing Standalone Activities
53+
54+
Start and manage Standalone Activities from your application code using the Temporal Client.
55+
56+
### Do not call from inside a Workflow
57+
58+
Don't call `client.ExecuteActivityAsync` / `client.StartActivityAsync` or any other Standalone Activity APIs from inside a Workflow Definition — use Workflow-side activity invocation (`Workflow.ExecuteActivityAsync`) instead.
59+
60+
### Connect a Client
61+
62+
The Standalone Activity operations are methods on a connected `TemporalClient`. The examples below assume this `client`.
63+
64+
```csharp
65+
using Temporalio.Client;
66+
using Temporalio.Common.EnvConfig;
67+
68+
var connectOptions = ClientEnvConfig.LoadClientConnectOptions();
69+
connectOptions.TargetHost ??= "localhost:7233";
70+
var client = await TemporalClient.ConnectAsync(connectOptions);
71+
```
72+
73+
### Execute (wait for result)
74+
75+
Use `client.ExecuteActivityAsync(...)` to durably enqueue the Activity, wait for it to run on a Worker, and return the result. The activity options require `Id`, `TaskQueue`, and at least one of `ScheduleToCloseTimeout` or `StartToCloseTimeout`.
76+
77+
#### With type checking
78+
79+
Use when activity definitions are available in this language. Pass a lambda invoking the activity method:
80+
81+
```csharp
82+
// In practice, use a meaningful business identifier, like customer or transaction identifier
83+
var activityId = Guid.NewGuid().ToString();
84+
85+
var result = await client.ExecuteActivityAsync(
86+
() => MyActivities.ComposeGreetingAsync(new ComposeGreetingInput("Hello", "World")),
87+
new(activityId, "standalone-activity-sample")
88+
{
89+
ScheduleToCloseTimeout = TimeSpan.FromSeconds(10),
90+
});
91+
```
92+
93+
#### Without type checking
94+
95+
Use when activity definitions are unavailable in this language (i.e. you can't import them). Pass the activity type name as a string and an argument array:
96+
97+
```csharp
98+
var result = await client.ExecuteActivityAsync<string>(
99+
"ComposeGreeting",
100+
new object?[] { new ComposeGreetingInput("Hello", "World") },
101+
new(activityId, "standalone-activity-sample")
102+
{
103+
ScheduleToCloseTimeout = TimeSpan.FromSeconds(10),
104+
});
105+
```
106+
107+
### Start (do not wait for result)
108+
109+
Use `client.StartActivityAsync(...)` to durably enqueue the Activity and get back a handle without waiting for completion. This takes the **exact same arguments as `ExecuteActivityAsync`**.
110+
111+
```csharp
112+
var handle = await client.StartActivityAsync(...);
113+
```
114+
115+
### Get a handle to an existing Activity execution
116+
117+
Use `client.GetActivityHandle(...)` to attach a handle to a previously started Standalone Activity. Passing `null` as the run ID (the default) targets the latest run of that Activity ID.
118+
119+
```csharp
120+
// Without a known result type
121+
var handle = client.GetActivityHandle("my-activity-id", runId: "the-run-id");
122+
123+
// With a known result type
124+
var typedHandle = client.GetActivityHandle<string>("my-activity-id", runId: "the-run-id");
125+
```
126+
127+
### Wait for the result of a handle
128+
129+
```csharp
130+
var result = await handle.GetResultAsync();
131+
```
132+
133+
Calling `ExecuteActivityAsync` is equivalent to `StartActivityAsync` followed by `await handle.GetResultAsync()`.
134+
135+
### List Standalone Activities
136+
137+
```csharp
138+
await foreach (var info in client.ListActivitiesAsync(
139+
"TaskQueue = 'standalone-activity-sample'")) // returns an IAsyncEnumerable<ActivityExecution>
140+
{
141+
Console.WriteLine(
142+
$"ActivityID: {info.ActivityId}, Type: {info.ActivityType}, Status: {info.Status}");
143+
}
144+
```
145+
146+
Only Standalone Activity Executions are returned; Activities running inside Workflows are not included.
147+
148+
### Count Standalone Activities
149+
150+
Use `client.CountActivitiesAsync(query)` to count matching executions; this takes the **exact same arguments as `ListActivitiesAsync`**.
151+
152+
```csharp
153+
var resp = await client.CountActivitiesAsync(
154+
"TaskQueue = 'standalone-activity-sample'");
155+
Console.WriteLine($"Total activities: {resp.Count}");
156+
```

references/java/java.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ See `references/java/testing.md` for info on writing tests.
263263
- **`references/java/advanced-features.md`** - Schedules, worker tuning, and more
264264
- **`references/java/data-handling.md`** - Data converters, Jackson, payload encryption
265265
- **`references/java/versioning.md`** - Patching API, workflow type versioning, Worker Versioning
266+
- **`references/java/standalone-activities.md`** - Standalone Activities: run an Activity directly from a Client without a Workflow (Public Preview). Concept overview at `references/core/standalone-activities.md`.
266267

267268
### Java Integrations
268269

0 commit comments

Comments
 (0)