Temporal is a distributed, scalable, durable, and highly available orchestration engine used to execute asynchronous, long-running business logic in a scalable and resilient way.
Temporal C++ SDK is a C++20 client library for authoring and running Temporal workflows, activities, and Nexus
operations. It wraps the shared Rust sdk-core engine via a C FFI bridge, providing fully asynchronous coroutine-based
APIs.
Status: Functionally Complete -- The SDK builds on MSVC 2022 (Windows) with 715/715 unit tests passing. All 3 self-contained examples run end-to-end against a live Temporal server with clean shutdown (exit code 0). Workflows execute activities, handle signals/queries/updates, use timers, and return results correctly. The public API uses typed arguments via variadic templates and DataConverter -- no raw JSON strings.
- C++20 coroutines --
co_await/co_returnfor all async operations (workflows, client calls, timers) - Lazy
Task<T>with symmetric transfer for efficient coroutine chaining - Deterministic
CoroutineSchedulerfor workflow replay (single-threaded FIFO, matching the Temporal execution model) - Full Temporal API coverage -- workflows, activities (with
execute_activity()), signals, queries, updates, child workflows, Nexus operations - Type-safe workflow/activity signatures via variadic template deduction -- no
std::anyin user code - DataConverter integration -- transparent serialization for all client and worker APIs
- Interceptor chains for both client-side and worker-side operations
- Protobuf integration -- 74 Temporal API proto files auto-generated at build time
- OpenTelemetry extension -- tracing interceptor for workflow/activity spans
- Diagnostics extension -- metrics adapter for counters, histograms, and gauges
- CMake 3.20+
- C++20 compiler: MSVC 2022+ (Windows), GCC 11+ or Clang 14+ (Linux)
- Rust toolchain (
cargo) -- for building thesdk-coreC bridge library - Git -- clone recursively to fetch the
sdk-coresubmodule
git clone --recursive https://github.com/temporalio/sdk-dotnet.git temporal-sdk-cpp
cd temporal-sdk-cppDependencies (abseil, protobuf, nlohmann/json, Google Test) are fetched automatically via CMake FetchContent.
Fast build with Ninja (recommended):
# Windows (uses C:\ninja\ninja.exe automatically)
cmake --preset windows-debug
cmake --build --preset windows-debug
# Linux
cmake --preset linux-debug
cmake --build --preset linux-debug
# Or manually specify Ninja
cmake -B cpp/build -S cpp -G "Ninja Multi-Config" -DCMAKE_MAKE_PROGRAM=C:/ninja/ninja.exe
cmake --build cpp/build --config Debug
# If Ninja is on PATH, no -DCMAKE_MAKE_PROGRAM needed
cmake -B cpp/build -S cpp -G "Ninja Multi-Config"
cmake --build cpp/build --config DebugStandard build (without Ninja):
# Configure
cmake -B cpp/build -S cpp
# Build the library
cmake --build cpp/build --target temporalio
# Build and run tests
cmake --build cpp/build --target temporalio_tests
ctest --test-dir cpp/build --output-on-failureTo skip optional components:
cmake -B cpp/build -S cpp \
-DTEMPORALIO_BUILD_EXTENSIONS=OFF \
-DTEMPORALIO_BUILD_EXAMPLES=OFF \
-DTEMPORALIO_BUILD_TESTS=OFFUser-local overrides (e.g., different Ninja path, different compiler) can be placed in
cpp/CMakeUserPresets.json, which is .gitignore'd.
#include <temporalio/client/temporal_client.h>
#include <temporalio/client/temporal_connection.h>
#include <temporalio/worker/temporal_worker.h>
#include <temporalio/workflows/workflow.h>
#include <temporalio/workflows/workflow_definition.h>
using namespace temporalio;
// Activity: takes a name, returns a greeting
coro::Task<std::string> greet(std::string name) {
co_return "Hello, " + name + "!";
}
// Workflow: calls the greet activity and returns its result
class GreetingWorkflow {
public:
coro::Task<std::string> run(std::string name) {
workflows::ActivityOptions opts;
opts.start_to_close_timeout = std::chrono::seconds(30);
co_return co_await workflows::Workflow::execute_activity<std::string>(
"greet", name, opts);
}
};
int main() {
// Connect to Temporal
auto tc = coro::run_task_sync(client::TemporalClient::connect(
{.connection = {.target_host = "localhost:7233"}}));
// Register activity and workflow
auto activity = activities::ActivityDefinition::create("greet", &greet);
auto workflow = workflows::WorkflowDefinition::create<GreetingWorkflow>(
"GreetingWorkflow").run(&GreetingWorkflow::run).build();
// Start worker on a background thread
worker::TemporalWorkerOptions opts;
opts.task_queue = "greeting-queue";
opts.activities.push_back(activity);
opts.workflows.push_back(workflow);
worker::TemporalWorker w(tc, opts);
std::stop_source stop;
std::jthread worker_thread([&w, token = stop.get_token()]() {
coro::run_task_sync(w.execute_async(token));
});
// Start workflow and get result (type-safe, no raw JSON)
client::WorkflowOptions wf_opts;
wf_opts.id = "my-workflow";
wf_opts.task_queue = "greeting-queue";
auto handle = coro::run_task_sync(tc->start_workflow(
"GreetingWorkflow", wf_opts, std::string("World")));
auto result = coro::run_task_sync(handle.get_result<std::string>());
std::cout << "Result: " << result << "\n";
// Clean shutdown on main thread
stop.request_stop();
worker_thread.join();
}cpp/
CMakeLists.txt # Top-level CMake build
CMakePresets.json # Ninja Multi-Config presets (windows/linux)
vcpkg.json # Dependency manifest
cmake/
Platform.cmake # Platform detection, Rust cargo build
CompilerWarnings.cmake # Warning flags
ProtobufGenerate.cmake # Proto code generation
temporalioConfig.cmake.in # CMake config template for find_package()
include/temporalio/ # Public headers (35+ files)
export.h # TEMPORALIO_EXPORT macro (GenerateExportHeader)
coro/ # Task<T>, CancellationToken, CoroutineScheduler, TaskCompletionSource
client/ # TemporalClient, TemporalConnection, WorkflowHandle
interceptors/ # IClientInterceptor, ClientOutboundInterceptor
common/ # RetryPolicy, SearchAttributes, MetricMeter, enums
converters/ # DataConverter, IPayloadConverter, IFailureConverter
exceptions/ # 20+ exception types (TemporalException hierarchy)
nexus/ # NexusServiceDefinition, OperationHandler
runtime/ # TemporalRuntime, telemetry config
testing/ # WorkflowEnvironment, ActivityEnvironment
worker/ # TemporalWorker, WorkflowInstance
interceptors/ # IWorkerInterceptor, inbound/outbound interceptors
internal/ # ActivityWorker, WorkflowWorker, NexusWorker
workflows/ # Workflow ambient API, WorkflowDefinition builder, ActivityOptions
src/temporalio/ # Private implementation (27 .cpp + 8 .h)
bridge/ # Rust FFI wrappers (SafeHandle, CallScope, interop)
extensions/
opentelemetry/ # TracingInterceptor
diagnostics/ # CustomMetricMeter
tests/ # Google Test suite (37 files, 715 tests)
examples/ # 6 examples (see Examples section below)
vcpkg-port/ # vcpkg port files for registry submission
vcpkg-overlay/ # Local development overlay port
test-consumer/ # Consumer integration test (find_package)
Six examples in cpp/examples/ demonstrate key Temporal patterns. All require a running Temporal server (temporal server start-dev).
| Example | What it demonstrates |
|---|---|
hello_world |
Client-only: connect and start a workflow |
signal_workflow |
Client-only: send signals and query workflow state |
activity_worker |
Worker-only: define and register activities |
workflow_activity |
E2E: workflow calls execute_activity(), returns result, clean shutdown |
timer_workflow |
E2E: deterministic timers, conditions, signals, queries |
update_workflow |
E2E: update handlers with validators, queries, signals |
# Build all examples
cmake --build cpp/build --config Debug
# Run an example (requires: temporal server start-dev)
./cpp/build/examples/Debug/example_workflow_activityUser Code (C++20)
|
Public API Layer (Client, Workflows, Activities, Worker, Testing, Converters)
|
Bridge Layer (SafeHandle + CallScope + FFI wrappers)
|
Native Library (temporal_sdk_core_c_bridge -- Rust, compiled via cargo)
| Concept | C++ Approach |
|---|---|
| Async/await | C++20 coroutines (co_await / co_return) with lazy Task<T> |
| Deterministic execution | CoroutineScheduler (FIFO deque, single-threaded) |
| Cancellation | std::stop_token / std::stop_source |
| Context propagation | thread_local pointers with RAII scopes |
| FFI memory management | CallScope (scoped allocations with std::deque for stable references) |
| Rust handle ownership | SafeHandle<T> RAII template with custom deleters |
| Callback-to-coroutine bridge | TaskCompletionSource<T> |
| Type-erased payloads | std::any |
| Shared ownership | std::shared_ptr (runtime, connection, client) |
| Pimpl pattern | std::unique_ptr<Impl> for ABI stability |
| Option | Default | Description |
|---|---|---|
TEMPORALIO_BUILD_EXTENSIONS |
ON |
Build OpenTelemetry and Diagnostics extensions |
TEMPORALIO_BUILD_TESTS |
ON |
Build the Google Test suite |
TEMPORALIO_BUILD_EXAMPLES |
ON |
Build example programs |
TEMPORALIO_BUILD_PROTOS |
ON |
Generate C++ types from Temporal API .proto files |
BUILD_SHARED_LIBS |
OFF |
Build as shared library instead of static |
| Library | Purpose | Source |
|---|---|---|
| Protobuf v29.3 | Temporal API types, bridge serialization | FetchContent or vcpkg |
| abseil-cpp | Required by Protobuf | FetchContent or vcpkg |
| nlohmann/json | JSON payload conversion | FetchContent or vcpkg |
| Google Test | Unit testing framework | FetchContent or vcpkg |
| OpenTelemetry C++ | Tracing extension (optional) | vcpkg |
| Rust toolchain | Building the sdk-core bridge library |
Pre-installed |
All dependencies except Rust are fetched automatically via CMake FetchContent if not found on the system.
# Build and run all tests
cmake --build cpp/build --target temporalio_tests
ctest --test-dir cpp/build --output-on-failure
# Run tests with verbose output
ctest --test-dir cpp/build --output-on-failure --verbose715 unit tests (all passing) cover:
- Async primitives (Task, CancellationToken, CoroutineScheduler, TaskCompletionSource)
- Bridge layer (CallScope, SafeHandle)
- Client (connection options, interceptors, workflow handle)
- Common types (enums, metrics, retry policy, search attributes)
- Converters (data converter, failure converter)
- Exceptions (full hierarchy)
- Nexus (operation handlers)
- Runtime (lifecycle)
- Testing utilities (activity environment, workflow environment)
- Worker (options, interceptors, workflow instance, replayer)
- Workflows (ambient API, definitions, info types)
Set environment variables to test against an external Temporal server:
export TEMPORAL_TEST_CLIENT_TARGET_HOST="localhost:7233"
export TEMPORAL_TEST_CLIENT_NAMESPACE="default"The SDK supports installation via vcpkg with find_package() integration.
vcpkg install temporalio --overlay-ports=./vcpkg-overlayfind_package(temporalio CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE temporalio::temporalio)Build as a shared library (DLL/SO) with proper symbol export via TEMPORALIO_EXPORT:
cmake -B cpp/build -S cpp -DBUILD_SHARED_LIBS=ON#include <temporalio/extensions/opentelemetry/tracing_interceptor.h>
// Add tracing to client and worker
auto tracing = std::make_shared<extensions::opentelemetry::TracingInterceptor>();
client_opts.interceptors.push_back(tracing);
worker_opts.interceptors.push_back(tracing);#include <temporalio/extensions/diagnostics/custom_metric_meter.h>
// Adapt Temporal metrics to OpenTelemetry Metrics API
auto meter = std::make_shared<extensions::diagnostics::CustomMetricMeter>(otel_meter);
runtime_opts.metrics.custom_meter = meter;This SDK is a fork of the Temporal .NET SDK.
The main branch tracks upstream C# releases; the cpp-conversion branch contains the C++ port.
A Claude Code slash command automates porting new upstream C# changes to C++:
Ensure the upstream remote is configured:
git remote add upstream https://github.com/temporalio/sdk-dotnet.gitFrom the cpp-conversion branch with a clean working tree:
# Preview what upstream changes exist (no modifications)
/update-cpp --dry-run
# Port all new upstream changes to C++
/update-cppThe command will:
- Fetch latest upstream C# changes
- Identify new/modified/deleted files since the last sync
- Categorize changes and present a summary for approval
- Merge upstream into your local branches
- Port each C# change to the corresponding C++ code
- Build and run the test suite
- Update
.claude/sync-state.jsonwith the new sync point
The file .claude/sync-state.json tracks the last upstream commit that was synced.
On the first run, it uses the initial port baseline (589c7c3) as the starting point.
Each sync appends to the history, recording what was ported and what was skipped.
This SDK is a C++20 port of the Temporal .NET SDK. The original C# SDK (~469 source files, ~412 tests) was systematically converted to idiomatic C++20, replacing:
- C#
async/awaitwith C++20 coroutines - .NET
Task<T>with a custom lazy coroutineTask<T> - P/Invoke with direct C FFI calls
CancellationTokenwithstd::stop_token- Reflection-based registration with template-based builders
- xUnit with Google Test
MIT License - see LICENSE for details.