Skip to main content

Quickstart

View Markdown

Standalone Activities are Activities that run independently, without being orchestrated by a Workflow. Instead of executing an Activity from within a Workflow Definition using ctx.execute_activity(), you execute a Standalone Activity directly from a Client.

The way you write the Activity and register it with a Worker is identical to Workflow Activities. The only difference is that you execute a Standalone Activity directly from your Temporal Client.

info

This documentation uses source code from the standalone_activities sample.

Get started with Standalone Activities

Prerequisites:

  • Rust 1.92.0+

  • Temporal Rust SDK (v1.0.0 or higher). See the Rust Quickstart for install instructions.

  • Temporal CLI v1.9.1 or higher. Install with Homebrew, or see the Temporal CLI install guide for other platforms. Verify the installation with temporal --version.

Start the Temporal development server with temporal server start-dev.

This command automatically starts the Temporal development server with the Web UI, and creates the default Namespace. It uses an in-memory database, so do not use it for real use cases.

The Temporal Server will now be available for client connections on localhost:7233, and the Temporal Web UI will now be accessible at http://localhost:8233.

brew install temporal
temporal --version
temporal server start-dev

Clone the sample

Clone the sdk-rust repository to follow along:

git clone https://github.com/temporalio/sdk-rust.git
cd sdk-rust/crates/sdk

The sample consists of separate programs in the crates/sdk/examples/standalone_activities directory:

standalone_activities/
├── activities.rs # Activity definition, shared by the programs below
├── worker.rs # Worker that processes Activity Tasks
├── execute_activity.rs # Executes an Activity and waits for the result
├── start_activity.rs # Starts an Activity without blocking
├── get_activity_handle.rs # Gets a handle to an existing Activity
├── list_activities.rs # Lists Activity Executions
└── count_activities.rs # Counts Activity Executions

To write the same code in your own project, add the dependencies shown here to your Cargo.toml. Standalone Activities need temporalio-client for the Client, temporalio-sdk and temporalio-macros for the Activity and Worker, and tokio as the async runtime. futures is needed to consume the stream returned by list_activities.

Cargo.toml
[dependencies]
futures = "0.3"
temporalio-client = "1.0.0"
temporalio-macros = "1.0.0"
temporalio-sdk = "1.0.0"
tokio = { version = "1", features = ["full"] }

Define your Activity

An Activity in the Temporal Rust SDK is an async method on an impl block marked with the #[activities] macro, annotated with #[activity]. The way you define a Standalone Activity is identical to how you define an Activity orchestrated by a Workflow. In fact, the same Activity can be executed both as a Standalone Activity and as a Workflow Activity.

Each Activity method takes an ActivityContext as its first parameter and returns Result<T, ActivityError>. Use _ctx if you don't need the context. To pass more than one value to an Activity, take them as a tuple, as compose_greeting does here.

By default, the macro names each Activity <ImplType>::<method_name>, so this one registers as GreetingActivities::compose_greeting. That's the name to use from the Temporal CLI and in List Filter queries.

activities.rs

activities.rs
use temporalio_macros::activities;
use temporalio_sdk::activities::{ActivityContext, ActivityError};

pub struct GreetingActivities;

#[activities]
impl GreetingActivities {
#[activity]
pub async fn compose_greeting(
_ctx: ActivityContext,
input: (String, String),
) -> Result<String, ActivityError> {
let (greeting, name) = input;
Ok(format!("{greeting}, {name}!"))
}
}

Run a Worker with the Activity registered

Running a Worker for Standalone Activities is the same as running a Worker for Workflow Activities — you build WorkerOptions for a Task Queue, register the Activities with register_activities, and call worker.run(). The Worker doesn't need to know whether the Activity will be invoked from a Workflow or as a Standalone Activity, and a Worker that runs Standalone Activities needs no registered Workflows at all. See How to run a Worker for more details on Worker setup and configuration options.

worker.rs

Open a new terminal, navigate to the crates/sdk directory, and run the Worker. Leave this terminal running — the Worker needs to stay up to process activities.

worker.rs
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let runtime = Runtime::from_current_tokio(Default::default())?;
let (conn_opts, client_opts) =
ClientOptions::load_from_config(LoadClientConfigProfileOptions::default())?;
let connection = Connection::connect(conn_opts).await?;
let client = Client::new(connection, client_opts)?;

// A Worker that only runs Standalone Activities needs no registered workflows.
let worker_options = WorkerOptions::new("standalone-activities")
.register_activities(GreetingActivities)
.build();

let mut worker = Worker::new(&runtime, client, worker_options)?;
println!("Worker started on task queue: standalone-activities");
worker.run().await?;

Ok(())
}
cargo run --features examples --example standalone-activities-worker

Execute a Standalone Activity

Use Client::start_activity to start a Standalone Activity, then ActivityHandle::result to block until it completes. Call these from your application code, not from inside a Workflow Definition. start_activity durably enqueues your Standalone Activity in the Temporal Server, and result waits for it to be executed on your Worker and returns the result.

execute_activity.rs

The first argument to start_activity is the Activity to run. Passing the Activity method itself, GreetingActivities::compose_greeting, gives you a typed ActivityHandle, so the input and output types are checked at compile time and result returns the Activity's own return type. The second argument is the Activity's input.

ActivityStartOptions requires a Task Queue, an Activity ID, and a close timeout. The with_start_to_close_timeout and with_schedule_to_close_timeout constructors return a builder with that timeout already set; call .build() to finish.

To run it:

  1. Make sure the Temporal Server is running (from the Get Started step above).
  2. Make sure the Worker is running (from the Run a Worker step above).
  3. Open a new terminal, navigate to the crates/sdk directory, and run the execute command.

Or use the Temporal CLI. Because compose_greeting takes its two values as a tuple, the CLI input is a single two-element JSON array.

execute_activity.rs
let options = ActivityStartOptions::with_start_to_close_timeout(
"standalone-activities",
"standalone-activity-id",
Duration::from_secs(10),
)
.build();

// There is no single "execute" call: start the activity, then await its result.
let handle = client
.start_activity(
GreetingActivities::compose_greeting,
("Hello".to_string(), "Temporal".to_string()),
options,
)
.await?;

let result = handle.result().await?;
println!("Activity result: {result}");
cargo run --features examples --example standalone-activities-execute
temporal activity execute \
--type 'GreetingActivities::compose_greeting' \
--activity-id standalone-activity-id \
--task-queue standalone-activities \
--start-to-close-timeout 10s \
--input '["Hello","Temporal"]'

Run with Temporal Cloud

All code samples on this page use ClientOptions::load_from_config to configure the Temporal Client connection. It responds to environment variables and TOML configuration files, so the same code works against a local dev server and Temporal Cloud without changes. See Run Standalone Activities with Temporal Cloud in the Feature Guide for mTLS and API key setup.

Next steps