diff --git a/docs/build-your-first-basic-workflow/build-your-first-workflow.mdx b/docs/build-your-first-basic-workflow/build-your-first-workflow.mdx
new file mode 100644
index 0000000000..5dcfe6d1ad
--- /dev/null
+++ b/docs/build-your-first-basic-workflow/build-your-first-workflow.mdx
@@ -0,0 +1,1996 @@
+---
+id: build-your-first-workflow
+title: Build a Financial Transaction Application
+sidebar_label: "Build a Financial Transaction Application"
+hide_table_of_contents: true
+description: Learn Temporal's core concepts by building a money transfer Workflow. Experience reliability, failure handling, and live debugging in a short tutorial.
+keywords:
+ - temporal
+ - workflow
+ - tutorial
+ - money transfer
+ - reliability
+tags:
+ - Getting Started
+ - Tutorial
+---
+
+import { CallToAction } from "@site/src/components/elements/CallToAction";
+import { StatusIndicators } from "@site/src/components/StatusIndicators";
+import { RetryPolicyComparison } from "@site/src/components/RetryPolicyComparison";
+import { NextButton } from "@site/src/components/TutorialNavigation";
+import { TutorialCodeWalkthrough, TutorialStep } from "@site/src/components/TutorialCodeWalkthrough";
+import { TutorialTabs, TutorialTab } from "@site/src/components/TutorialTabs";
+import { SetupSteps, SetupStep, CodeSnippet } from "@site/src/components/elements/SetupSteps";
+import SdkTabs from "@site/src/components/elements/SdkTabs";
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+
+
+{/* ─── Part 1 ─── */}
+
+
+You'll build a money movement app that withdraws from one account, deposits to another, and refunds if the deposit fails. You'll run it against a local Temporal server and watch it complete in the UI.
+
+### Before you begin
+
+
+
+## 1. Clone the project
+
+
+
+
+```bash
+git clone https://github.com/temporalio/money-transfer-project-template-python
+cd money-transfer-project-template-python
+python3 -m venv .venv
+source .venv/bin/activate
+pip install temporalio
+```
+
+:::note Python virtual environment
+Python 3.12 and later requires packages to be installed inside a virtual environment. The `source .venv/bin/activate` command activates it in your current terminal. Run this in each new terminal window you open for this project.
+:::
+
+
+
+
+```bash
+git clone https://github.com/temporalio/money-transfer-project-template-go
+cd money-transfer-project-template-go
+```
+
+
+
+
+```bash
+git clone https://github.com/temporalio/money-transfer-project-template-java
+cd money-transfer-project-template-java
+```
+
+:::note Prerequisites
+This project requires a JDK (Java 8+) and Maven. If `java -version` or `mvn -version` returns "command not found", install them before continuing.
+:::
+
+
+
+
+```bash
+git clone https://github.com/temporalio/money-transfer-project-template-ts
+cd money-transfer-project-template-ts
+npm install
+```
+
+
+
+
+```bash
+git clone https://github.com/temporalio/money-transfer-project-template-dotnet
+cd money-transfer-project-template-dotnet
+```
+
+:::note Prerequisites
+This project requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download). Run `dotnet --version` to confirm it is installed.
+:::
+
+
+
+
+```bash
+git clone https://github.com/temporalio/money-transfer-project-template-ruby
+cd money-transfer-project-template-ruby
+bundle install
+```
+
+:::note Prerequisites
+This project requires Ruby 3.1+ and Bundler. Run `ruby --version` and `bundle --version` to confirm both are installed.
+:::
+
+
+
+
+:::tip
+Each repo is a GitHub Template. Clone it to your own account as a starting point for your own application.
+:::
+
+## 2. Explore the code
+
+A Temporal application has three parts: a **Workflow** that orchestrates the steps, **Activities** that do the actual work, and a **Worker** that runs both.
+
+### Workflow
+
+The Workflow takes a `PaymentDetails` input (source account, target account, amount, reference ID) defined in a shared file, and uses it to call each Activity in turn. Walk through the Workflow code step by step. Select your SDK in the tab row, then use **Next** to move through each piece.
+
+All snippets below come from your Workflow file:
+
+
+
+
+[workflows.py](https://github.com/temporalio/money-transfer-project-template-python/blob/main/workflows.py)
+
+
+
+
+[workflow.go](https://github.com/temporalio/money-transfer-project-template-go/blob/main/workflow.go)
+
+
+
+
+[src/main/java/moneytransferapp/MoneyTransferWorkflowImpl.java](https://github.com/temporalio/money-transfer-project-template-java/blob/main/src/main/java/moneytransferapp/MoneyTransferWorkflowImpl.java)
+
+
+
+
+[src/workflows.ts](https://github.com/temporalio/money-transfer-project-template-ts/blob/main/src/workflows.ts)
+
+
+
+
+[MoneyTransferWorker/Workflow.cs](https://github.com/temporalio/money-transfer-project-template-dotnet/blob/main/MoneyTransferWorker/Workflow.cs)
+
+
+
+
+[workflow.rb](https://github.com/temporalio/money-transfer-project-template-ruby/blob/main/workflow.rb)
+
+
+
+
+
+
+
+
+
+
+
+```python
+retry_policy = RetryPolicy(
+ maximum_attempts=3,
+ maximum_interval=timedelta(seconds=2),
+ non_retryable_error_types=[
+ "InvalidAccountError",
+ "InsufficientFundsError",
+ ],
+)
+```
+
+`InvalidAccountError` and `InsufficientFundsError` are non-retryable because retrying won't fix them. The input is wrong. A transient network error is what retries are for.
+
+
+
+
+```go
+retrypolicy := &temporal.RetryPolicy{
+ InitialInterval: time.Second,
+ BackoffCoefficient: 2.0,
+ MaximumInterval: 100 * time.Second,
+ MaximumAttempts: 500, // 0 = unlimited
+ NonRetryableErrorTypes: []string{"InvalidAccountError", "InsufficientFundsError"},
+}
+
+options := workflow.ActivityOptions{
+ StartToCloseTimeout: time.Minute,
+ RetryPolicy: retrypolicy,
+}
+ctx = workflow.WithActivityOptions(ctx, options)
+```
+
+`BackoffCoefficient: 2.0` means Temporal doubles the wait between each attempt: 1s, 2s, 4s, up to the `MaximumInterval` cap.
+
+
+
+
+```java
+private final RetryOptions retryoptions = RetryOptions.newBuilder()
+ .setInitialInterval(Duration.ofSeconds(1))
+ .setMaximumInterval(Duration.ofSeconds(20))
+ .setBackoffCoefficient(2)
+ .setMaximumAttempts(5000)
+ .build();
+
+private final ActivityOptions defaultActivityOptions = ActivityOptions.newBuilder()
+ .setRetryOptions(retryoptions)
+ .setStartToCloseTimeout(Duration.ofSeconds(2))
+ .setScheduleToCloseTimeout(Duration.ofSeconds(5000))
+ .build();
+```
+
+
+
+
+```typescript
+const { withdraw, deposit, refund } = proxyActivities({
+ retry: {
+ initialInterval: '1 second',
+ maximumInterval: '1 minute',
+ backoffCoefficient: 2,
+ maximumAttempts: 500,
+ nonRetryableErrorTypes: ['InvalidAccountError', 'InsufficientFundsError'],
+ },
+ startToCloseTimeout: '1 minute',
+});
+```
+
+
+
+
+```csharp
+var options = new ActivityOptions
+{
+ StartToCloseTimeout = TimeSpan.FromMinutes(5),
+ RetryPolicy = new()
+ {
+ InitialInterval = TimeSpan.FromSeconds(1),
+ MaximumInterval = TimeSpan.FromSeconds(100),
+ BackoffCoefficient = 2,
+ MaximumAttempts = 3,
+ NonRetryableErrorTypes = new[] { "InvalidAccountException", "InsufficientFundsException" }
+ }
+};
+```
+
+
+
+
+```ruby
+retry_policy = Temporalio::RetryPolicy.new(
+ max_interval: 10,
+ non_retryable_error_types: ['InvalidAccountError', 'InsufficientFundsError']
+)
+
+activity_options = {
+ start_to_close_timeout: 10,
+ retry_policy:
+}
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+```python
+withdraw_output = await workflow.execute_activity_method(
+ BankingActivities.withdraw,
+ payment_details,
+ start_to_close_timeout=timedelta(seconds=5),
+ retry_policy=retry_policy,
+)
+```
+
+
+
+
+```go
+var withdrawOutput string
+withdrawErr := workflow.ExecuteActivity(ctx, Withdraw, input).Get(ctx, &withdrawOutput)
+if withdrawErr != nil {
+ return "", withdrawErr
+}
+```
+
+
+
+
+```java
+try {
+ accountActivityStub.withdraw(sourceAccountId, transactionReferenceId, amountToTransfer);
+} catch (Exception e) {
+ System.out.printf("[%s] Withdrawal of $%d from account %s failed",
+ transactionReferenceId, amountToTransfer, sourceAccountId);
+ return; // stop; nothing to compensate
+}
+```
+
+
+
+
+```typescript
+let withdrawResult: string;
+try {
+ withdrawResult = await withdraw(details);
+} catch (withdrawErr) {
+ throw new ApplicationFailure(`Withdrawal failed. Error: ${withdrawErr}`);
+}
+```
+
+
+
+
+```csharp
+var withdrawResult = await Workflow.ExecuteActivityAsync(
+ (BankingActivities act) => act.WithdrawAsync(details), options);
+```
+
+
+
+
+```ruby
+withdraw_result = Temporalio::Workflow.execute_activity(
+ BankActivities::Withdraw, details, **activity_options
+)
+```
+
+
+
+
+
+
+
+
+
+
+
+```python
+try:
+ deposit_output = await workflow.execute_activity_method(
+ BankingActivities.deposit,
+ payment_details,
+ start_to_close_timeout=timedelta(seconds=5),
+ retry_policy=retry_policy,
+ )
+ return f"Transfer complete (transaction IDs: {withdraw_output}, {deposit_output})"
+except ActivityError as deposit_err:
+ workflow.logger.error(f"Deposit failed: {deposit_err}")
+ # → falls through to refund
+```
+
+
+
+
+```go
+var depositOutput string
+depositErr := workflow.ExecuteActivity(ctx, Deposit, input).Get(ctx, &depositOutput)
+if depositErr != nil {
+ // deposit failed; need to compensate
+}
+```
+
+
+
+
+```java
+try {
+ accountActivityStub.deposit(destinationAccountId, transactionReferenceId, amountToTransfer);
+ System.out.printf("[%s] Transaction succeeded.\n", transactionReferenceId);
+ return;
+} catch (Exception e) {
+ System.out.printf("[%s] Deposit of $%d to account %s failed.\n",
+ transactionReferenceId, amountToTransfer, destinationAccountId);
+ // → falls through to refund
+}
+```
+
+
+
+
+```typescript
+let depositResult: string;
+try {
+ depositResult = await deposit(details);
+} catch (depositErr) {
+ // deposit failed; try to refund
+}
+```
+
+
+
+
+```csharp
+try
+{
+ var depositResult = await Workflow.ExecuteActivityAsync(
+ () => BankingActivities.DepositAsync(details),
+ new ActivityOptions { StartToCloseTimeout = TimeSpan.FromMinutes(5), RetryPolicy = retryPolicy });
+ return $"Transfer complete (transaction IDs: {withdrawResult}, {depositResult})";
+}
+catch (Exception depositEx)
+{
+ // → falls through to refund
+}
+```
+
+
+
+
+```ruby
+begin
+ deposit_result = Temporalio::Workflow.execute_activity(
+ BankActivities::Deposit, details, **activity_options
+ )
+ "Transfer complete (transaction IDs: #{withdraw_result}, #{deposit_result})"
+rescue Temporalio::Error::ActivityError
+ # → falls through to refund
+end
+```
+
+
+
+
+
+
+
+
+
+
+
+```python
+# inside the except block after a failed deposit
+try:
+ refund_output = await workflow.execute_activity_method(
+ BankingActivities.refund,
+ payment_details,
+ start_to_close_timeout=timedelta(seconds=5),
+ retry_policy=retry_policy,
+ )
+ workflow.logger.info(f"Refund successful. Confirmation ID: {refund_output}")
+except ActivityError as refund_error:
+ workflow.logger.error(f"Refund failed: {refund_error}")
+ raise refund_error from deposit_err
+
+raise deposit_err # re-raise after successful refund
+```
+
+
+
+
+```go
+// inside the depositErr != nil block
+var refundResult string
+refundErr := workflow.ExecuteActivity(ctx, Refund, input).Get(ctx, &refundResult)
+if refundErr != nil {
+ return "", fmt.Errorf("deposit failed: %v. Refund also failed: %w",
+ depositErr, refundErr)
+}
+return "", fmt.Errorf("deposit failed into %v: money returned to %v: %w",
+ input.TargetAccount, input.SourceAccount, depositErr)
+```
+
+
+
+
+```java
+// after deposit fails, try to refund
+try {
+ System.out.printf("[%s] Refunding $%d to account %s.\n",
+ transactionReferenceId, amountToTransfer, sourceAccountId);
+ accountActivityStub.refund(sourceAccountId, transactionReferenceId, amountToTransfer);
+ System.out.printf("[%s] Refund successful. No transfer made.\n", transactionReferenceId);
+} catch (Exception e) {
+ System.out.printf("[%s] Refund also failed.\n", transactionReferenceId);
+ throw e;
+}
+```
+
+
+
+
+```typescript
+// inside the depositErr catch block
+try {
+ await refund(details);
+} catch (refundErr) {
+ throw ApplicationFailure.create({
+ message: `Deposit failed and refund also failed. Cause: ${refundErr}`,
+ });
+}
+throw ApplicationFailure.create({
+ message: `Deposit failed. Money returned to ${details.sourceAccount}.`,
+});
+```
+
+
+
+
+```csharp
+// inside the catch (Exception depositEx) block
+string refundResult = await Workflow.ExecuteActivityAsync(
+ () => BankingActivities.RefundAsync(details),
+ new ActivityOptions { StartToCloseTimeout = TimeSpan.FromMinutes(5), RetryPolicy = retryPolicy });
+throw new ApplicationFailureException(
+ $"Deposit failed. Money returned to {details.SourceAccount}.", depositEx);
+```
+
+
+
+
+```ruby
+# inside the ActivityError rescue block
+Temporalio::Workflow.execute_activity(
+ BankActivities::Refund, details, **activity_options
+)
+raise # re-raise after successful refund
+```
+
+
+
+
+
+
+
+
+### Activities
+
+Activities do the real work. Each one calls an external service. If an Activity fails, Temporal retries it. The three Activities in this app: Withdraw, Deposit, and Refund.
+
+
+
+
+
+
+
+
+
+[activities.py](https://github.com/temporalio/money-transfer-project-template-python/blob/main/activities.py)
+```py
+import asyncio
+
+from temporalio import activity
+
+from banking_service import BankingService, InvalidAccountError
+from shared import PaymentDetails
+
+
+class BankingActivities:
+ def __init__(self):
+ self.bank = BankingService("bank-api.example.com")
+
+ @activity.defn
+ async def withdraw(self, data: PaymentDetails) -> str:
+ reference_id = f"{data.reference_id}-withdrawal"
+ try:
+ confirmation = await asyncio.to_thread(
+ self.bank.withdraw, data.source_account, data.amount, reference_id
+ )
+ return confirmation
+ except InvalidAccountError:
+ raise
+ except Exception:
+ activity.logger.exception("Withdrawal failed")
+ raise
+
+```
+
+
+
+
+
+
+[activity.go](https://github.com/temporalio/money-transfer-project-template-go/blob/main/activity.go)
+```go
+func Withdraw(ctx context.Context, data PaymentDetails) (string, error) {
+ log.Printf("Withdrawing $%d from account %s.\n\n",
+ data.Amount,
+ data.SourceAccount,
+ )
+
+ referenceID := fmt.Sprintf("%s-withdrawal", data.ReferenceID)
+ bank := BankingService{"bank-api.example.com"}
+ confirmation, err := bank.Withdraw(data.SourceAccount, data.Amount, referenceID)
+ return confirmation, err
+}
+
+```
+
+
+
+
+
+[src/main/java/moneytransferapp/AccountActivityImpl.java](https://github.com/temporalio/money-transfer-project-template-java/blob/main/src/main/java/moneytransferapp/AccountActivityImpl.java)
+```java
+@Override
+public void withdraw(String accountId, String referenceId, int amount) {
+ System.out.printf("\nWithdrawing $%d from account %s.\n[ReferenceId: %s]\n", amount, accountId, referenceId);
+ System.out.flush();
+}
+```
+
+
+
+
+
+[src/activities.ts](https://github.com/temporalio/money-transfer-project-template-ts/blob/main/src/activities.ts)
+```ts
+import type { PaymentDetails } from './shared';
+import { BankingService } from './banking-client';
+
+export async function withdraw(details: PaymentDetails): Promise {
+ console.log(`Withdrawing $${details.amount} from account ${details.sourceAccount}.\n\n`);
+ const bank1 = new BankingService('bank1.example.com');
+ return await bank1.withdraw(details.sourceAccount, details.amount, details.referenceId);
+}
+```
+
+
+
+
+
+[MoneyTransferWorker/Activities.cs](https://github.com/temporalio/money-transfer-project-template-dotnet/blob/main/MoneyTransferWorker/Activities.cs)
+```cs
+[Activity]
+public static async Task WithdrawAsync(PaymentDetails details)
+{
+ Console.WriteLine($"Withdrawing ${details.Amount} from account {details.SourceAccount}.");
+ var bank = new BankingService("bank1.example.com");
+ return await bank.WithdrawAsync(details.SourceAccount, details.Amount, details.ReferenceId);
+}
+```
+
+
+
+
+
+[activities.rb](https://github.com/temporalio/money-transfer-project-template-ruby/blob/main/activities.rb)
+```rb
+class Withdraw < Temporalio::Activity::Definition
+ def execute(details)
+ bank = BankingService.new('bank1.example.com')
+ bank.withdraw(details.source_account, details.amount, details.reference_id)
+ end
+end
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+[activities.py](https://github.com/temporalio/money-transfer-project-template-python/blob/main/activities.py)
+```py
+ @activity.defn
+ async def deposit(self, data: PaymentDetails) -> str:
+ reference_id = f"{data.reference_id}-deposit"
+ try:
+ confirmation = await asyncio.to_thread(
+ self.bank.deposit, data.target_account, data.amount, reference_id
+ )
+ """
+ confirmation = await asyncio.to_thread(
+ self.bank.deposit_that_fails,
+ data.target_account,
+ data.amount,
+ reference_id,
+ )
+ """
+ return confirmation
+ except InvalidAccountError:
+ raise
+ except Exception:
+ activity.logger.exception("Deposit failed")
+ raise
+
+```
+
+
+
+
+
+
+[activity.go](https://github.com/temporalio/money-transfer-project-template-go/blob/main/activity.go)
+```go
+func Deposit(ctx context.Context, data PaymentDetails) (string, error) {
+ log.Printf("Depositing $%d into account %s.\n\n",
+ data.Amount,
+ data.TargetAccount,
+ )
+
+ referenceID := fmt.Sprintf("%s-deposit", data.ReferenceID)
+ bank := BankingService{"bank-api.example.com"}
+ // Uncomment the next line and comment the one after that to simulate an unknown failure
+ // confirmation, err := bank.DepositThatFails(data.TargetAccount, data.Amount, referenceID)
+ confirmation, err := bank.Deposit(data.TargetAccount, data.Amount, referenceID)
+ return confirmation, err
+}
+
+```
+
+
+
+
+
+[src/main/java/moneytransferapp/AccountActivityImpl.java](https://github.com/temporalio/money-transfer-project-template-java/blob/main/src/main/java/moneytransferapp/AccountActivityImpl.java)
+```java
+@Override
+public void deposit(String accountId, String referenceId, int amount) {
+ boolean activityShouldSucceed = true;
+ if (!activityShouldSucceed) {
+ System.out.println("Deposit failed");
+ System.out.flush();
+ throw Activity.wrap(new RuntimeException("Simulated Activity error during deposit of funds"));
+ }
+ System.out.printf("\nDepositing $%d into account %s.\n[ReferenceId: %s]\n", amount, accountId, referenceId);
+ System.out.flush();
+}
+```
+
+
+
+
+
+[src/activities.ts](https://github.com/temporalio/money-transfer-project-template-ts/blob/main/src/activities.ts)
+```ts
+export async function deposit(details: PaymentDetails): Promise {
+ console.log(`Depositing $${details.amount} into account ${details.targetAccount}.\n\n`);
+ const bank2 = new BankingService('bank2.example.com');
+ // Uncomment to simulate a failure for Part 2:
+ // return await bank2.depositThatFails(details.targetAccount, details.amount, details.referenceId);
+ return await bank2.deposit(details.targetAccount, details.amount, details.referenceId);
+}
+```
+
+
+
+
+
+[MoneyTransferWorker/Activities.cs](https://github.com/temporalio/money-transfer-project-template-dotnet/blob/main/MoneyTransferWorker/Activities.cs)
+```cs
+[Activity]
+public static async Task DepositAsync(PaymentDetails details)
+{
+ Console.WriteLine($"Depositing ${details.Amount} into account {details.TargetAccount}.");
+ var bank = new BankingService("bank2.example.com");
+
+ // Uncomment below and comment out the try-catch block to simulate a failure for Part 2:
+ /*
+ return await bank.DepositThatFailsAsync(details.TargetAccount, details.Amount, details.ReferenceId);
+ */
+
+ try
+ {
+ return await bank.DepositAsync(details.TargetAccount, details.Amount, details.ReferenceId);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationFailureException("Deposit failed", ex);
+ }
+}
+```
+
+
+
+
+
+[activities.rb](https://github.com/temporalio/money-transfer-project-template-ruby/blob/main/activities.rb)
+```rb
+class Deposit < Temporalio::Activity::Definition
+ def execute(details)
+ bank = BankingService.new('bank2.example.com')
+ # Uncomment to simulate a failure for Part 2:
+ # bank.deposit_that_fails(details.target_account, details.amount, details.reference_id)
+ bank.deposit(details.target_account, details.amount, details.reference_id)
+ end
+end
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+[activities.py](https://github.com/temporalio/money-transfer-project-template-python/blob/main/activities.py)
+```py
+ @activity.defn
+ async def refund(self, data: PaymentDetails) -> str:
+ reference_id = f"{data.reference_id}-refund"
+ try:
+ confirmation = await asyncio.to_thread(
+ self.bank.deposit, data.source_account, data.amount, reference_id
+ )
+ return confirmation
+ except InvalidAccountError:
+ raise
+ except Exception:
+ activity.logger.exception("Refund failed")
+ raise
+
+```
+
+
+
+
+
+
+[activity.go](https://github.com/temporalio/money-transfer-project-template-go/blob/main/activity.go)
+```go
+func Refund(ctx context.Context, data PaymentDetails) (string, error) {
+ log.Printf("Refunding $%v back into account %v.\n\n",
+ data.Amount,
+ data.SourceAccount,
+ )
+
+ referenceID := fmt.Sprintf("%s-refund", data.ReferenceID)
+ bank := BankingService{"bank-api.example.com"}
+ confirmation, err := bank.Deposit(data.SourceAccount, data.Amount, referenceID)
+ return confirmation, err
+}
+
+```
+
+
+
+
+
+[src/main/java/moneytransferapp/AccountActivityImpl.java](https://github.com/temporalio/money-transfer-project-template-java/blob/main/src/main/java/moneytransferapp/AccountActivityImpl.java)
+```java
+@Override
+public void refund(String accountId, String referenceId, int amount) {
+ boolean activityShouldSucceed = true;
+ if (!activityShouldSucceed) {
+ System.out.println("Refund failed");
+ System.out.flush();
+ throw Activity.wrap(new RuntimeException("Simulated Activity error during refund to source account"));
+ }
+ System.out.printf("\nRefunding $%d to account %s.\n[ReferenceId: %s]\n", amount, accountId, referenceId);
+ System.out.flush();
+}
+```
+
+
+
+
+
+[src/activities.ts](https://github.com/temporalio/money-transfer-project-template-ts/blob/main/src/activities.ts)
+```ts
+export async function refund(details: PaymentDetails): Promise {
+ console.log(`Refunding $${details.amount} to account ${details.sourceAccount}.\n\n`);
+ const bank1 = new BankingService('bank1.example.com');
+ return await bank1.deposit(details.sourceAccount, details.amount, details.referenceId);
+}
+```
+
+
+
+
+
+[MoneyTransferWorker/Activities.cs](https://github.com/temporalio/money-transfer-project-template-dotnet/blob/main/MoneyTransferWorker/Activities.cs)
+```cs
+[Activity]
+public static async Task RefundAsync(PaymentDetails details)
+{
+ Console.WriteLine($"Refunding ${details.Amount} to account {details.SourceAccount}.");
+ var bank = new BankingService("bank1.example.com");
+ return await bank.RefundAsync(details.SourceAccount, details.Amount, details.ReferenceId);
+}
+```
+
+
+
+
+
+[activities.rb](https://github.com/temporalio/money-transfer-project-template-ruby/blob/main/activities.rb)
+```rb
+class Refund < Temporalio::Activity::Definition
+ def execute(details)
+ bank = BankingService.new('bank1.example.com')
+ bank.deposit(details.source_account, details.amount, details.reference_id)
+ end
+end
+```
+
+
+
+
+
+
+
+
+
+### Worker
+
+The Worker is the process that runs your Workflow and Activity code. It connects to Temporal, polls a Task Queue, and executes tasks as they arrive. The Task Queue name must match what the Client uses to start the Workflow. A mismatch silently prevents execution.
+
+
+
+
+
+[run_worker.py](https://github.com/temporalio/money-transfer-project-template-python/blob/main/run_worker.py)
+```py
+import asyncio
+
+from temporalio.client import Client
+from temporalio.worker import Worker
+
+from activities import BankingActivities
+from shared import MONEY_TRANSFER_TASK_QUEUE_NAME
+from workflows import MoneyTransfer
+
+
+async def main() -> None:
+ client: Client = await Client.connect("localhost:7233", namespace="default")
+ # Run the worker
+ activities = BankingActivities()
+ worker: Worker = Worker(
+ client,
+ task_queue=MONEY_TRANSFER_TASK_QUEUE_NAME,
+ workflows=[MoneyTransfer],
+ activities=[activities.withdraw, activities.deposit, activities.refund],
+ )
+ await worker.run()
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+
+
+[worker/main.go](https://github.com/temporalio/money-transfer-project-template-go/blob/main/worker/main.go)
+```go
+func main() {
+
+ c, err := client.Dial(client.Options{})
+ if err != nil {
+ log.Fatalln("Unable to create Temporal client.", err)
+ }
+ defer c.Close()
+
+ w := worker.New(c, app.MoneyTransferTaskQueueName, worker.Options{})
+
+ // This worker hosts both Workflow and Activity functions.
+ w.RegisterWorkflow(app.MoneyTransfer)
+ w.RegisterActivity(app.Withdraw)
+ w.RegisterActivity(app.Deposit)
+ w.RegisterActivity(app.Refund)
+
+ // Start listening to the Task Queue.
+ err = w.Run(worker.InterruptCh())
+ if err != nil {
+ log.Fatalln("unable to start Worker", err)
+ }
+}
+```
+
+
+
+
+
+
+[src/main/java/moneytransferapp/MoneyTransferWorker.java](https://github.com/temporalio/money-transfer-project-template-java/blob/main/src/main/java/moneytransferapp/MoneyTransferWorker.java)
+```java
+package moneytransferapp;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+import io.temporal.worker.Worker;
+import io.temporal.worker.WorkerFactory;
+
+public class MoneyTransferWorker {
+
+ public static void main(String[] args) {
+ // Create a stub that accesses a Temporal Service on the local development machine
+ WorkflowServiceStubs serviceStub = WorkflowServiceStubs.newLocalServiceStubs();
+
+ // The Worker uses the Client to communicate with the Temporal Service
+ WorkflowClient client = WorkflowClient.newInstance(serviceStub);
+
+ // A WorkerFactory creates Workers
+ WorkerFactory factory = WorkerFactory.newInstance(client);
+
+ // A Worker listens to one Task Queue.
+ // This Worker processes both Workflows and Activities
+ Worker worker = factory.newWorker(Shared.MONEY_TRANSFER_TASK_QUEUE);
+
+ // Register a Workflow implementation with this Worker
+ // The implementation must be known at runtime to dispatch Workflow tasks
+ // Workflows are stateful so a type is needed to create instances.
+ worker.registerWorkflowImplementationTypes(MoneyTransferWorkflowImpl.class);
+
+ // Register Activity implementation(s) with this Worker.
+ // The implementation must be known at runtime to dispatch Activity tasks
+ // Activities are stateless and thread safe so a shared instance is used.
+ worker.registerActivitiesImplementations(new AccountActivityImpl());
+
+ System.out.println("Worker is running and actively polling the Task Queue.");
+ System.out.println("To quit, use ^C to interrupt.");
+
+ // Start all registered Workers. The Workers will start polling the Task Queue.
+ factory.start();
+ }
+}
+```
+
+
+
+
+
+
+[src/worker.ts](https://github.com/temporalio/money-transfer-project-template-ts/blob/main/src/worker.ts)
+```ts
+import { Worker } from '@temporalio/worker';
+import * as activities from './activities';
+import { namespace, taskQueueName } from './shared';
+
+async function run() {
+ const worker = await Worker.create({
+ workflowsPath: require.resolve('./workflows'),
+ activities,
+ namespace,
+ taskQueue: taskQueueName,
+ });
+ await worker.run();
+}
+
+run().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
+```
+
+
+
+
+
+
+[MoneyTransferWorker/Program.cs](https://github.com/temporalio/money-transfer-project-template-dotnet/blob/main/MoneyTransferWorker/Program.cs)
+```cs
+// This file is designated to run the worker
+using Temporalio.Client;
+using Temporalio.Worker;
+using Temporalio.MoneyTransferProject.MoneyTransferWorker;
+
+// Create a client to connect to localhost on "default" namespace
+var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
+
+// Cancellation token to shutdown worker on ctrl+c
+using var tokenSource = new CancellationTokenSource();
+Console.CancelKeyPress += (_, eventArgs) =>
+{
+ tokenSource.Cancel();
+ eventArgs.Cancel = true;
+};
+
+// Create an instance of the activities since we have instance activities.
+// If we had all static activities, we could just reference those directly.
+var activities = new BankingActivities();
+
+// Create a worker with the activity and workflow registered
+using var worker = new TemporalWorker(
+ client, // client
+ new TemporalWorkerOptions(taskQueue: "MONEY_TRANSFER_TASK_QUEUE")
+ .AddAllActivities(activities) // Register activities
+ .AddWorkflow() // Register workflow
+);
+
+// Run the worker until it's cancelled
+Console.WriteLine("Running worker...");
+try
+{
+ await worker.ExecuteAsync(tokenSource.Token);
+}
+catch (OperationCanceledException)
+{
+ Console.WriteLine("Worker cancelled");
+}
+```
+
+
+
+
+
+
+[worker.rb](https://github.com/temporalio/money-transfer-project-template-ruby/blob/main/worker.rb)
+```rb
+require_relative 'activities'
+require_relative 'shared'
+require_relative 'workflow'
+require 'logger'
+require 'temporalio/client'
+require 'temporalio/worker'
+
+# Create a Temporal Client that connects to a local Temporal Service, uses
+# a Namespace called 'default', and displays log messages to standard output
+client = Temporalio::Client.connect(
+ 'localhost:7233',
+ 'default',
+ logger: Logger.new($stdout, level: Logger::INFO)
+)
+
+# Create a Worker that polls the specified Task Queue and can
+# fulfill requests for the specified Workflow and Activities
+worker = Temporalio::Worker.new(
+ client:,
+ task_queue: MoneyTransfer::TASK_QUEUE_NAME,
+ workflows: [MoneyTransfer::MoneyTransferWorkflow],
+ activities: [MoneyTransfer::BankActivities::Withdraw,
+ MoneyTransfer::BankActivities::Deposit,
+ MoneyTransfer::BankActivities::Refund]
+)
+
+# Start the Worker, which will poll the Task Queue until stopped
+puts 'Starting Worker (press Ctrl+C to exit)'
+worker.run(shutdown_signals: ['SIGINT'])
+```
+
+
+
+
+
+### Client
+
+The Client is what starts a Workflow Execution. It connects to Temporal, submits the request with a unique Workflow ID and Task Queue name, then waits for the result. The Workflow ID makes each execution idempotent. Submitting the same ID twice returns the existing execution instead of starting a new one.
+
+
+
+
+
+[run_workflow.py](https://github.com/temporalio/money-transfer-project-template-python/blob/main/run_workflow.py)
+```py
+import asyncio
+import traceback
+
+from temporalio.client import Client, WorkflowFailureError
+
+from shared import MONEY_TRANSFER_TASK_QUEUE_NAME, PaymentDetails
+from workflows import MoneyTransfer
+
+
+async def main() -> None:
+ client: Client = await Client.connect("localhost:7233")
+
+ data: PaymentDetails = PaymentDetails(
+ source_account="85-150",
+ target_account="43-812",
+ amount=250,
+ reference_id="12345",
+ )
+
+ try:
+ result = await client.execute_workflow(
+ MoneyTransfer.run,
+ data,
+ id="pay-invoice-701",
+ task_queue=MONEY_TRANSFER_TASK_QUEUE_NAME,
+ )
+ print(f"Result: {result}")
+ except WorkflowFailureError:
+ print("Got expected exception: ", traceback.format_exc())
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+
+
+[start/main.go](https://github.com/temporalio/money-transfer-project-template-go/blob/main/start/main.go)
+```go
+func main() {
+ // Create the client object just once per process
+ c, err := client.Dial(client.Options{})
+
+ if err != nil {
+ log.Fatalln("Unable to create Temporal client:", err)
+ }
+
+ defer c.Close()
+
+ input := app.PaymentDetails{
+ SourceAccount: "85-150",
+ TargetAccount: "43-812",
+ Amount: 250,
+ ReferenceID: "12345",
+ }
+
+ options := client.StartWorkflowOptions{
+ ID: "pay-invoice-701",
+ TaskQueue: app.MoneyTransferTaskQueueName,
+ }
+
+ log.Printf("Starting transfer from account %s to account %s for %d", input.SourceAccount, input.TargetAccount, input.Amount)
+
+ we, err := c.ExecuteWorkflow(context.Background(), options, app.MoneyTransfer, input)
+ if err != nil {
+ log.Fatalln("Unable to start the Workflow:", err)
+ }
+
+ log.Printf("WorkflowID: %s RunID: %s\n", we.GetID(), we.GetRunID())
+
+ var result string
+
+ err = we.Get(context.Background(), &result)
+
+ if err != nil {
+ log.Fatalln("Unable to get Workflow result:", err)
+ }
+
+ log.Println(result)
+}
+```
+
+
+
+
+
+
+[src/main/java/moneytransferapp/TransferApp.java](https://github.com/temporalio/money-transfer-project-template-java/blob/main/src/main/java/moneytransferapp/TransferApp.java)
+```java
+package moneytransferapp;
+
+import io.temporal.api.common.v1.WorkflowExecution;
+import io.temporal.client.WorkflowClient;
+import io.temporal.client.WorkflowOptions;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+
+import java.security.SecureRandom;
+import java.time.Instant;
+import java.util.UUID;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class TransferApp {
+ private static final SecureRandom random;
+
+ static {
+ // Seed the random number generator with nano date
+ random = new SecureRandom();
+ random.setSeed(Instant.now().getNano());
+ }
+
+ public static String randomAccountIdentifier() {
+ return IntStream.range(0, 9)
+ .mapToObj(i -> String.valueOf(random.nextInt(10)))
+ .collect(Collectors.joining());
+ }
+
+ public static void main(String[] args) throws Exception {
+
+ // In the Java SDK, a stub represents an element that participates in
+ // Temporal orchestration and communicates using gRPC.
+
+ // A WorkflowServiceStubs communicates with the Temporal front-end service.
+ WorkflowServiceStubs serviceStub = WorkflowServiceStubs.newLocalServiceStubs();
+
+ // A WorkflowClient wraps the stub.
+ // It can be used to start, signal, query, cancel, and terminate Workflows.
+ WorkflowClient client = WorkflowClient.newInstance(serviceStub);
+
+ // Workflow options configure Workflow stubs.
+ // A WorkflowId prevents duplicate instances, which are removed.
+ WorkflowOptions options = WorkflowOptions.newBuilder()
+ .setTaskQueue(Shared.MONEY_TRANSFER_TASK_QUEUE)
+ .setWorkflowId("money-transfer-workflow")
+ .build();
+
+ // WorkflowStubs enable calls to methods as if the Workflow object is local
+ // but actually perform a gRPC call to the Temporal Service.
+ MoneyTransferWorkflow workflow = client.newWorkflowStub(MoneyTransferWorkflow.class, options);
+
+ // Configure the details for this money transfer request
+ String referenceId = UUID.randomUUID().toString().substring(0, 18);
+ String fromAccount = randomAccountIdentifier();
+ String toAccount = randomAccountIdentifier();
+ int amountToTransfer = ThreadLocalRandom.current().nextInt(15, 75);
+ TransactionDetails transaction = new CoreTransactionDetails(fromAccount, toAccount, referenceId, amountToTransfer);
+
+ // Perform asynchronous execution.
+ // This process exits after making this call and printing details.
+ WorkflowExecution we = WorkflowClient.start(workflow::transfer, transaction);
+
+ System.out.printf("\nMONEY TRANSFER PROJECT\n\n");
+ System.out.printf("Initiating transfer of $%d from [Account %s] to [Account %s].\n\n",
+ amountToTransfer, fromAccount, toAccount);
+ System.out.printf("[WorkflowID: %s]\n[RunID: %s]\n[Transaction Reference: %s]\n\n", we.getWorkflowId(), we.getRunId(), referenceId);
+ System.exit(0);
+ }
+}
+```
+
+
+
+
+
+
+[src/client.ts](https://github.com/temporalio/money-transfer-project-template-ts/blob/main/src/client.ts)
+```ts
+import { Connection, Client } from '@temporalio/client';
+import { moneyTransfer } from './workflows';
+import type { PaymentDetails } from './shared';
+
+import { namespace, taskQueueName } from './shared';
+
+async function run() {
+ const connection = await Connection.connect();
+ const client = new Client({ connection, namespace });
+
+ const details: PaymentDetails = {
+ amount: 400,
+ sourceAccount: '85-150',
+ targetAccount: '43-812',
+ referenceId: '12345',
+ };
+
+ console.log(
+ `Starting transfer from account ${details.sourceAccount} to account ${details.targetAccount} for $${details.amount}`
+ );
+
+ const handle = await client.workflow.start(moneyTransfer, {
+ args: [details],
+ taskQueue: taskQueueName,
+ workflowId: 'pay-invoice-801',
+ });
+
+ console.log(
+ `Started Workflow ${handle.workflowId} with RunID ${handle.firstExecutionRunId}`
+ );
+ console.log(await handle.result());
+
+ connection.close()
+}
+
+run().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
+```
+
+
+
+
+
+
+[MoneyTransferClient/Program.cs](https://github.com/temporalio/money-transfer-project-template-dotnet/blob/main/MoneyTransferClient/Program.cs)
+```cs
+// This file is designated to run the workflow
+using Temporalio.MoneyTransferProject.MoneyTransferWorker;
+using Temporalio.Client;
+
+// Connect to the Temporal server
+var client = await TemporalClient.ConnectAsync(new("localhost:7233") { Namespace = "default" });
+
+// Define payment details
+var details = new PaymentDetails(
+ SourceAccount: "85-150",
+ TargetAccount: "43-812",
+ Amount: 400,
+ ReferenceId: "12345"
+);
+
+Console.WriteLine($"Starting transfer from account {details.SourceAccount} to account {details.TargetAccount} for ${details.Amount}");
+
+var workflowId = $"pay-invoice-{Guid.NewGuid()}";
+
+try
+{
+ // Start the workflow
+ var handle = await client.StartWorkflowAsync(
+ (MoneyTransferWorkflow wf) => wf.RunAsync(details),
+ new(id: workflowId, taskQueue: "MONEY_TRANSFER_TASK_QUEUE"));
+
+ Console.WriteLine($"Started Workflow {workflowId}");
+
+ // Await the result of the workflow
+ var result = await handle.GetResultAsync();
+ Console.WriteLine($"Workflow result: {result}");
+}
+catch (Exception ex)
+{
+ Console.Error.WriteLine($"Workflow execution failed: {ex.Message}");
+}
+```
+
+
+
+
+
+
+[starter.rb](https://github.com/temporalio/money-transfer-project-template-ruby/blob/main/starter.rb)
+```rb
+require_relative 'shared'
+require_relative 'workflow'
+require 'securerandom'
+require 'temporalio/client'
+
+# Create the Temporal Client that the Worker will use to connect to the
+# Temporal Service (in this case, it will connect to one running locally,
+# on the standard port, and use the default namespace)
+client = Temporalio::Client.connect('localhost:7233', 'default')
+
+# Default values for the payment details (can override via positional commandline parameters)
+details = MoneyTransfer::TransferDetails.new('A1001', 'B2002', 100, SecureRandom.uuid)
+details.source_account = ARGV[0] if ARGV.length >= 1
+details.target_account = ARGV[1] if ARGV.length >= 2
+details.amount = ARGV[2].to_i if ARGV.length >= 3
+details.reference_id = ARGV[3] if ARGV.length >= 4
+
+# Use the Temporal Client to submit a Workflow Execution request to the
+# Temporal Service, wait for the result returned by executing the Workflow,
+# and then display that value to standard output.
+handle = client.start_workflow(
+ MoneyTransfer::MoneyTransferWorkflow,
+ details,
+ id: "moneytransfer-#{details.reference_id}",
+ task_queue: MoneyTransfer::TASK_QUEUE_NAME
+)
+
+puts "Initiated transfer of $#{details.amount} from #{details.source_account} to #{details.target_account}"
+puts "Workflow ID: #{handle.id}"
+
+# Keep running (and retry) if the Temporal Service becomes unavailable
+begin
+ puts "Workflow result: #{handle.result}"
+rescue Temporalio::Error::RPCError
+ puts 'Temporal Service unavailable while awaiting result'
+ retry
+end
+```
+
+
+
+
+
+## 3. Run the application
+
+You need three terminals. Start them in order.
+
+
+
+temporal server start-dev --db-filename ./temporal.db
+}>
+
+### Terminal 1: Temporal server
+
+Start the local Temporal server. This runs the scheduler, state store, and Web UI at [localhost:8233](http://localhost:8233). The `--db-filename` flag persists Workflow state to disk, which is required for the crash recovery demo in Part 2.
+
+:::note Port conflict
+If you get an `address already in use` error, a previous process may still hold port 7233. Run `lsof -ti:7233 | xargs kill` on macOS/Linux to clear it.
+:::
+
+
+
+
+
+ {`source .venv/bin/activate\npython run_worker.py`}
+
+
+ go run worker/main.go
+
+
+ mvn compile exec:java -Dexec.mainClass="moneytransferapp.MoneyTransferWorker" -Dorg.slf4j.simpleLogger.defaultLogLevel=warn
+
+
+ npm run worker
+
+
+ dotnet run --project MoneyTransferWorker/MoneyTransferWorker.csproj
+
+
+ bundle exec ruby worker.rb
+
+
+}>
+
+### Terminal 2: Worker
+
+Start the Worker. It connects to Temporal, polls the Task Queue, and executes your Workflow and Activity code. Leave it running.
+
+:::note Java: first-run compile time
+Maven downloads the Temporal SDK and its dependencies on the first run. This can take a few minutes on a fresh machine. Subsequent runs compile from cache and start in seconds.
+:::
+
+
+
+
+
+ {`source .venv/bin/activate\npython run_workflow.py`}
+ Result: Transfer complete (transaction IDs: Withdrew $250 from account 85-150. ReferenceId: 12345, Deposited $250 into account 43-812. ReferenceId: 12345)
+
+
+ go run start/main.go
+ Transfer complete (transaction IDs: W1779185060, D1779185060)
+
+
+ mvn compile exec:java -Dexec.mainClass="moneytransferapp.TransferApp"
+ {`Withdrawing $[AMOUNT] from account [SOURCE].\nDepositing $[AMOUNT] into account [DEST].\n[[UUID]] Transaction succeeded.`}
+
+
+ npm run client
+ Transfer complete (transaction IDs: W3436600150, D9270097234)
+
+
+ dotnet run --project MoneyTransferClient/MoneyTransferClient.csproj
+ Workflow result: Transfer complete (transaction IDs: W-caa90e06-..., D-1910468b-...)
+
+
+ bundle exec ruby starter.rb
+ Workflow result: Transfer complete (transaction IDs: OKW-100-A1001, OKD-100-B2002)
+
+
+}>
+
+### Terminal 3: Start the Workflow
+
+Run the client to submit a Workflow Execution. You should see a transfer result printed when it completes.
+
+
+
+
+
+
+
+## 4. View in the Temporal UI
+
+The Temporal Web UI shows the full event history: every Activity scheduled, started, and completed, plus the Workflow input and result.
+
+
+
+
+
+
+
+Click on the Workflow in the list to see the full event history. In Part 2, you'll use that history to debug a live failure.
+
+
+
+{/* ─── Part 2 ─── */}
+
+
+You'll take down the server while a Workflow is running to see what Temporal preserves, then break the deposit Activity and fix it live without losing state.
+
+## Scenario A: Crash recovery
+
+The goal is to show that Temporal stores Workflow state durably. State lives on the server, not in the Worker process, and survives server restarts.
+
+
+
+Ctrl+C in Terminal 2
+}>
+
+### Step 1: Stop the Worker
+
+If the Worker from Part 1 is still running, stop it. You're going to start a Workflow with no Worker running.
+
+
+
+
+
+ {`source .venv/bin/activate\npython run_workflow.py`}
+
+
+ go run start/main.go
+
+
+ mvn compile exec:java -Dexec.mainClass="moneytransferapp.TransferApp"
+
+
+ npm run client
+
+
+ dotnet run --project MoneyTransferClient/MoneyTransferClient.csproj
+
+
+ bundle exec ruby starter.rb
+
+
+}>
+
+### Step 2: Start the Workflow
+
+In Terminal 3, submit a new Workflow Execution. Since no Worker is running yet, the Workflow is accepted but not executed. The client process will hang while it waits for a result.
+
+:::note Java
+`TransferApp` exits immediately after submitting the Workflow and does not wait for the result. That's expected. The Workflow is still Running in Temporal.
+:::
+
+
+
+Open localhost:8233
+}>
+
+### Step 3: Check the Web UI
+
+The Workflow is listed as **Running**. Temporal has recorded the Execution and is holding its state, even though no Worker has done any work yet.
+
+
+
+Ctrl+C in Terminal 1
+}>
+
+### Step 4: Kill the Temporal server
+
+Stop the local server you started in Part 1.
+
+
+
+temporal server start-dev --db-filename ./temporal.db
+}>
+
+### Step 5: Restart the Temporal server
+
+If you see a port conflict error, wait a few seconds and try again. The previous process may still be releasing port 7233.
+
+
+
+Reload localhost:8233
+}>
+
+### Step 6: Check the Web UI again
+
+The Workflow is still **Running**. The server crashed and restarted, and the Workflow survived as you left it.
+
+
+
+
+
+
+
+Workflow state lives on the Temporal server, not in your Worker process. A Worker crash, a server restart, or a network blip will not lose the transaction.
+
+Leave the Workflow running. You'll resolve it in Scenario B.
+
+## Scenario B: Fix a bug in a running Workflow
+
+The Workflow from Scenario A is still pending. The Worker is stopped. You're going to introduce a failing deposit, watch Temporal retry, then deploy a fix to the in-flight Workflow.
+
+### Step 1: Introduce the failing deposit
+
+Open the deposit Activity and switch to the failing version:
+
+
+
+
+In **activities.py**, the `deposit()` method currently has `deposit_that_fails` wrapped in triple-quoted strings. Remove the triple quotes and comment out the working `deposit` call instead, so the method body looks like this:
+
+```python
+try:
+ # confirmation = await asyncio.to_thread(
+ # self.bank.deposit, data.target_account, data.amount, reference_id
+ # )
+ confirmation = await asyncio.to_thread(
+ self.bank.deposit_that_fails,
+ data.target_account,
+ data.amount,
+ reference_id,
+ )
+ return confirmation
+except InvalidAccountError:
+ raise
+except Exception:
+ activity.logger.exception("Deposit failed")
+ raise
+```
+
+
+
+
+In **activity.go**, swap `Deposit` for `DepositThatFails`:
+
+```go
+// Comment out:
+// confirmation, err := bank.Deposit(data.TargetAccount, data.Amount, referenceID)
+
+// Uncomment:
+confirmation, err := bank.DepositThatFails(data.TargetAccount, data.Amount, referenceID)
+```
+
+
+
+
+In **`src/main/java/moneytransferapp/AccountActivityImpl.java`**, set `activityShouldSucceed` to `false`:
+
+```java
+boolean activityShouldSucceed = false;
+```
+
+
+
+
+In **src/activities.ts**, uncomment lines 25-29 and comment out lines 30-34:
+
+```typescript
+ // Uncomment this block:
+ return await bank2.depositThatFails(
+ details.targetAccount,
+ details.amount,
+ details.referenceId
+ );
+ // Comment out this block:
+ // return await bank2.deposit(
+ // details.targetAccount,
+ // details.amount,
+ // details.referenceId
+ // );
+```
+
+
+
+
+In **`MoneyTransferWorker/Activities.cs`**, the `DepositAsync` method has a block comment around `DepositThatFailsAsync`. Remove the `/*` and `*/` markers to enable it, then comment out the `try { ... } catch { ... }` block below it:
+
+```csharp
+// Remove the /* and */ to uncomment:
+return await bankService.DepositThatFailsAsync(details.TargetAccount, details.Amount, details.ReferenceId);
+
+// Comment out the try-catch block:
+// try
+// {
+// return await bankService.DepositAsync(...);
+// }
+// catch (Exception ex)
+// {
+// throw new ApplicationFailureException("Deposit failed", ex);
+// }
+```
+
+
+
+
+In **activities.rb**, add a `raise` to the top of the `Deposit` class's `execute` method:
+
+```ruby
+class Deposit < Temporalio::Activity::Definition
+ def execute(details)
+ raise 'Simulated deposit failure'
+ puts("Doing a deposit into #{details.target_account} for #{details.amount}")
+ raise InvalidAccountError, 'Invalid account number' if details.target_account == 'B5555'
+ "OKD-#{details.amount}-#{details.target_account}"
+ end
+end
+```
+
+
+
+
+### Step 2: Start the Worker
+
+In Terminal 2, start the Worker:
+
+
+
+
+```bash
+python run_worker.py
+```
+
+
+
+
+```bash
+go run worker/main.go
+```
+
+
+
+
+```bash
+mvn compile exec:java -Dexec.mainClass="moneytransferapp.MoneyTransferWorker" -Dorg.slf4j.simpleLogger.defaultLogLevel=warn
+```
+
+
+
+
+```bash
+npm run worker
+```
+
+
+
+
+```bash
+dotnet run --project MoneyTransferWorker/MoneyTransferWorker.csproj
+```
+
+
+
+
+```bash
+bundle exec ruby worker.rb
+```
+
+
+
+
+The Worker picks up the pending Workflow from Scenario A. The Withdraw Activity succeeds. Then the Deposit Activity runs and fails. Temporal retries it according to the retry policy. You'll see attempts accumulating in the Worker logs and in the Web UI event history. The Workflow stays **Running**. It's stuck retrying, but nothing is lost.
+
+
+
+:::note Python and .NET: move quickly
+The retry policy in the Python and .NET templates sets `maximum_attempts: 3`. Once all three attempts are exhausted, the Workflow triggers the refund and then fails. You won't be able to resume it. Stop the Worker and apply your fix before the third retry fires. If the Workflow does fail before you finish, you can still inspect the full event history in the Web UI (including the Refund Activity). To run the scenario again, change the Workflow ID in your client script and start fresh.
+:::
+
+### Step 3: Fix the bug and redeploy
+
+Stop the Worker (`Ctrl+C`) and revert your change. Put the working deposit line back and remove (or re-wrap) the failing version. Then restart the Worker in Terminal 2:
+
+
+
+
+```bash
+python run_worker.py
+```
+
+
+
+
+```bash
+go run worker/main.go
+```
+
+
+
+
+```bash
+mvn compile exec:java -Dexec.mainClass="moneytransferapp.MoneyTransferWorker" -Dorg.slf4j.simpleLogger.defaultLogLevel=warn
+```
+
+
+
+
+```bash
+npm run worker
+```
+
+
+
+
+```bash
+dotnet run --project MoneyTransferWorker/MoneyTransferWorker.csproj
+```
+
+
+
+
+```bash
+bundle exec ruby worker.rb
+```
+
+
+
+
+On the next retry, the fixed Activity code runs. The deposit succeeds. The Workflow completes.
+
+Where to see the result depends on your SDK:
+
+- **Python / Ruby**: Terminal 3 is still running and prints the transfer result when the Workflow completes.
+- **Go / TypeScript**: Terminal 3 exited when the server was killed in Step 4. Check Terminal 2 (Worker logs) or the Web UI event history for the completed result.
+- **Java**: Terminal 3 already exited after submitting the Workflow. Check Terminal 2 (Worker logs) or the Web UI.
+- **.NET**: Terminal 3 is still running and prints the transfer result when the Workflow completes.
+
+:::tip
+You just fixed a bug in a running application without losing state or restarting the transaction. Temporal stored every completed step, so Withdraw was not re-run. Execution resumed from the first failing Activity and completed from there.
+:::
+
+
+
+## What you've learned
+
+- Temporal stores Workflow state durably. A crashed Worker or server restart doesn't lose the transaction.
+- Activity retries are automatic. A failing step keeps retrying until it succeeds or hits your policy limits.
+- Completed Activities aren't re-executed on retry. Temporal knows which steps finished.
+- You can fix Activity code and redeploy while a Workflow is running. It picks up from where it failed.
+
+## Continue learning
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/build-your-first-basic-workflow/index.mdx b/docs/build-your-first-basic-workflow/index.mdx
new file mode 100644
index 0000000000..e38e618658
--- /dev/null
+++ b/docs/build-your-first-basic-workflow/index.mdx
@@ -0,0 +1,20 @@
+---
+id: index
+title: Build a Financial Transaction Application
+sidebar_label: Build a Financial Transaction Application
+description: Learn Temporal's core concepts by building and running a money transfer Workflow that demonstrates reliability, failure handling, and live debugging.
+hide_table_of_contents: true
+keywords:
+ - temporal
+ - workflow
+ - getting started
+ - tutorial
+ - money transfer
+tags:
+ - Getting Started
+ - Tutorial
+---
+
+import { Redirect } from '@docusaurus/router';
+
+
diff --git a/docs/build-your-first-basic-workflow/simulate-failure.mdx b/docs/build-your-first-basic-workflow/simulate-failure.mdx
new file mode 100644
index 0000000000..0d07bc50d5
--- /dev/null
+++ b/docs/build-your-first-basic-workflow/simulate-failure.mdx
@@ -0,0 +1,1030 @@
+---
+id: simulate-failure
+title: Simulate Failures with Temporal
+sidebar_label: "Part 2: Simulate Failure"
+description: Learn how Temporal handles failures, recovers from crashes, and enables live debugging of your Workflows.
+hide_table_of_contents: true
+keywords:
+ - temporal
+ - python
+ - simulate failure
+ - crash recovery
+ - live debugging
+ - reliability
+tags:
+ - Getting Started
+ - Tutorial
+---
+
+import { CallToAction } from "@site/src/components/elements/CallToAction";
+import { TemporalProgress } from "@site/src/components/TemporalProgress";
+import { StatusIndicators } from "@site/src/components/StatusIndicators";
+import { WorkflowDiagram } from "@site/src/components/WorkflowDiagram";
+import { RetryCounter } from "@site/src/components/RetryCounter";
+import { TemporalCheckbox } from "@site/src/components/TemporalCheckbox";
+import SdkTabs from "@site/src/components/elements/SdkTabs";
+import { SetupSteps, SetupStep, CodeSnippet } from "@site/src/components/elements/SetupSteps";
+import { CodeComparison } from "@site/src/components/CodeComparison";
+import { AnimatedTerminal } from "@site/src/components/AnimatedTerminal";
+
+export const getTodayDate = () => {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = String(now.getMonth() + 1).padStart(2, '0');
+ const day = String(now.getDate()).padStart(2, '0');
+ const hours = String(now.getHours()).padStart(2, '0');
+ const minutes = String(now.getMinutes()).padStart(2, '0');
+ const seconds = String(now.getSeconds()).padStart(2, '0');
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
+};
+
+export const getTodayDateISO = () => {
+ return new Date().toISOString();
+};
+
+# Part 2: Simulate Failure
+In this part, you'll simulate failures to see how Temporal handles them.
+This demonstrates why Temporal is particularly useful for building reliable systems.
+
+
+
+Systems fail in unpredictable ways. A seemingly harmless deployment can bring down production, a database connection can time out during peak traffic, or a third-party service may experience an outage.
+Despite our best efforts with comprehensive testing and monitoring, systems are inherently unpredictable and complex.
+Networks fail, servers restart unexpectedly, and dependencies we trust can become unavailable without warning.
+
+Traditional systems aren't equipped to handle these realities.
+When something fails halfway through a multi-step process, you're left with partial state, inconsistent data, and the complex task of figuring out where things went wrong and how to recover.
+Most applications either lose progress entirely or require you to build extensive checkpointing and recovery logic.
+
+In this tutorial, you'll see Temporal's Durable Execution in action by running two tests: crashing a server while it's working and fixing code problems on the fly without stopping your application.
+
+## Recover from a server crash
+
+Unlike other solutions, Temporal is designed with failure in mind.
+In this part of the tutorial, you'll simulate a server crash mid-transaction and watch Temporal helps you recover from it.
+
+**Here's the challenge:** Kill your Worker process while money is being transferred.
+In traditional systems, this would corrupt the transaction or lose data entirely.
+
+
+
+### Before You Start
+
+
+ Worker is currently stopped
+
+
+
+ You have terminals ready (Terminal 2 for Worker, Terminal 3 for Workflow)
+
+
+
+ Web UI is open at `http://localhost:8233`
+
+
+
+What's happening behind the scenes?
+
+Unlike many modern applications that require complex leader election processes and external databases to handle failure, Temporal automatically preserves the state of your Workflow even if the server is down.
+You can test this by stopping the Temporal Service while a Workflow Execution is in progress.
+
+No data is lost once the Temporal Service went offline.
+When it comes back online, the work picked up where it left off before the outage.
+Keep in mind that this example uses a single instance of the service running on a single machine.
+In a production deployment, the Temporal Service can be deployed as a cluster, spread across several machines for higher availability and increased throughput.
+
+
+
+### Instructions
+
+
+
+
+
+ Terminal 2 - Worker
+
+
+
+ python run_worker.py
+
+
+ go run worker/main.go
+
+
+ mvn compile exec:java -Dexec.mainClass="moneytransferapp.MoneyTransferWorker"
+
+
+ npm run worker
+
+
+ dotnet run --project MoneyTransferWorker/MoneyTransferWorker.csproj
+
+
+ bundle exec ruby worker.rb
+
+
+
+}>
+
+### Step 1: Start Your Worker
+
+First, stop any running Worker (`Ctrl+C`) and start a fresh one in Terminal 2.
+
+
+
+
+
+
+
+ Terminal 3 - Workflow
+
+
+
+ python run_workflow.py
+
+
+ go run start/main.go
+
+
+ mvn compile exec:java -Dexec.mainClass="moneytransferapp.TransferApp"
+
+
+ npm run client
+
+
+ dotnet run --project MoneyTransferClient/MoneyTransferClient.csproj
+
+
+ bundle exec ruby starter.rb
+
+
+
+}>
+
+### Step 2: Start the Workflow
+
+Now in Terminal 3, start the Workflow. Check the Web UI - you'll see your Worker busy executing the Workflow and its Activities.
+
+
+
+
+
+
+ The Crash Test
+
Go back to Terminal 2 and kill the Worker with Ctrl+C
+
+} style={{background: 'transparent'}}>
+
+### Step 3: Simulate the Crash
+
+**The moment of truth!** Kill your Worker while it's processing the transaction.
+
+**Jump back to the Web UI** and refresh. Your Workflow is still showing as "Running"!
+
+That's the magic! The Workflow keeps running because Temporal saved its state, even though we killed the Worker.
+
+
+
+
+
+
+
+ Terminal 2 - Recovery
+
+
+
+ python run_worker.py
+
+
+ go run worker/main.go
+
+
+ mvn compile exec:java -Dexec.mainClass="moneytransferapp.MoneyTransferWorker"
+
+
+ npm run worker
+
+
+ dotnet run --project MoneyTransferWorker/MoneyTransferWorker.csproj
+
+
+ bundle exec ruby worker.rb
+
+
+
+}>
+
+### Step 4: Bring Your Worker Back
+
+Restart your Worker in Terminal 2. Watch Terminal 3 - you'll see the Workflow finish up and show the result!
+
+
+
+
+
+
+
+
+:::tip **Try This Challenge**
+
+Try killing the Worker at different points during execution. Start the Workflow, kill the Worker during the withdrawal, then restart it. Kill it during the deposit. Each time, notice how Temporal maintains perfect state consistency.
+
+Check the Web UI while the Worker is down and you'll see the Workflow is still "Running" even though no code is executing.
+:::
+
+## Recover from an unknown error
+
+In this part of the tutorial, you will inject a bug into your production code, watch Temporal retry automatically, then fix the bug while the Workflow is still running.
+This demo application makes a call to an external service in an Activity.
+If that call fails due to a bug in your code, the Activity produces an error.
+
+To test this out and see how Temporal responds, you'll simulate a bug in one of the Activity functions or methods.
+
+
+
+### Before You Start
+
+
+ Worker is stopped
+
+
+
+ Code editor open with the Activities file
+
+
+
+ Ready to uncomment the failure line
+
+
+
+ Web UI open to watch the retries
+
+
+
+## Instructions
+
+### Step 1: Stop Your Worker
+
+Before we can simulate a failure, we need to stop the current Worker process. This allows us to modify the Activity code safely.
+
+In Terminal 2 (where your Worker is running), stop it with `Ctrl+C`.
+
+**What's happening?** You're about to modify Activity code to introduce a deliberate failure. The Worker process needs to restart to pick up code changes, but the Workflow Execution will continue running in Temporal's service - this separation between execution state and code is a core Temporal concept.
+
+### Step 2: Introduce the Bug
+
+Now we'll intentionally introduce a failure in the deposit Activity to simulate real-world scenarios like network timeouts, database connection issues, or external service failures. This demonstrates how Temporal handles partial failures in multi-step processes.
+
+
+
+
+Find the `deposit()` method and **uncomment the failing line** while **commenting out the working line**:
+
+**activities.py**
+```python
+@activity.defn
+async def deposit(self, data: PaymentDetails) -> str:
+ reference_id = f"{data.reference_id}-deposit"
+ try:
+ # Comment out this working line:
+ # confirmation = await asyncio.to_thread(
+ # self.bank.deposit, data.target_account, data.amount, reference_id
+ # )
+
+ # Uncomment this failing line:
+ confirmation = await asyncio.to_thread(
+ self.bank.deposit_that_fails,
+ data.target_account,
+ data.amount,
+ reference_id,
+ )
+ return confirmation
+ except InvalidAccountError:
+ raise
+ except Exception:
+ activity.logger.exception("Deposit failed")
+ raise
+```
+
+Save your changes. You've now created a deliberate failure point in your deposit Activity. This simulates a real-world scenario where external service calls might fail intermittently.
+
+
+
+
+
+Find the `Deposit()` function and **uncomment the failing line** while **commenting out the working line**:
+
+**activity.go**
+```go
+func Deposit(ctx context.Context, data PaymentDetails) (string, error) {
+ log.Printf("Depositing $%d into account %s.\n\n",
+ data.Amount,
+ data.TargetAccount,
+ )
+
+ referenceID := fmt.Sprintf("%s-deposit", data.ReferenceID)
+ bank := BankingService{"bank-api.example.com"}
+
+ // Uncomment this failing line:
+ confirmation, err := bank.DepositThatFails(data.TargetAccount, data.Amount, referenceID)
+ // Comment out this working line:
+ // confirmation, err := bank.Deposit(data.TargetAccount, data.Amount, referenceID)
+
+ return confirmation, err
+}
+```
+
+Save your changes. You've now created a deliberate failure point in your deposit Activity. This simulates a real-world scenario where external service calls might fail intermittently.
+
+
+
+
+
+Open `AccountActivityImpl.java` and find the `deposit()` method. **Change `activityShouldSucceed` to `false`**:
+
+**src/main/java/moneytransferapp/AccountActivityImpl.java**
+```java
+@Override
+public void deposit(String accountId, String referenceId, int amount) {
+ // Change this to false to simulate failure:
+ boolean activityShouldSucceed = false;
+
+ if (activityShouldSucceed) {
+ System.out.printf("\nDepositing $%d into account %s.\n[ReferenceId: %s]\n",
+ amount, accountId, referenceId);
+ } else {
+ System.out.println("Deposit failed");
+ throw new RuntimeException("Simulated deposit failure");
+ }
+}
+```
+
+Save your changes. You've now created a deliberate failure point in your deposit Activity. This simulates a real-world scenario where external service calls might fail intermittently.
+
+
+
+
+
+Find the `deposit()` function in `activities.ts` and **comment out the working line** while **uncommenting the failing line**:
+
+**src/activities.ts**
+```typescript
+export async function deposit(details: PaymentDetails): Promise {
+ console.log(
+ `Depositing $${details.amount} into account ${details.targetAccount}.\n\n`
+ );
+ const bank = new BankingService('bank2.example.com');
+
+ // Comment out this working line:
+ // return await bank.deposit(details.targetAccount, details.amount, details.referenceId);
+
+ // Uncomment this failing line:
+ return await bank.depositThatFails(details.targetAccount, details.amount, details.referenceId);
+}
+```
+
+Save your changes. You've now created a deliberate failure point in your deposit Activity. This simulates a real-world scenario where external service calls might fail intermittently.
+
+
+
+
+
+Find the `DepositAsync()` method and **uncomment the failing line** while **commenting out the working block**:
+
+**MoneyTransferWorker/Activities.cs**
+```csharp
+[Activity]
+public static async Task DepositAsync(PaymentDetails details)
+{
+ var bankService = new BankingService("bank2.example.com");
+ Console.WriteLine($"Depositing ${details.Amount} into account {details.TargetAccount}.");
+
+ // Uncomment this failing line:
+ return await bankService.DepositThatFailsAsync(details.TargetAccount, details.Amount, details.ReferenceId);
+
+ // Comment out this working block:
+ /*
+ try
+ {
+ return await bankService.DepositAsync(details.TargetAccount, details.Amount, details.ReferenceId);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationFailureException("Deposit failed", ex);
+ }
+ */
+}
+```
+
+Save your changes. You've now created a deliberate failure point in your deposit Activity. This simulates a real-world scenario where external service calls might fail intermittently.
+
+
+
+
+
+Find the `Withdraw` class in `activities.rb` and **uncomment the failing line** that causes a divide-by-zero error:
+
+**activities.rb**
+```ruby
+class Withdraw < Temporalio::Activity::Definition
+ def execute(details)
+ puts("Doing a withdrawal from #{details.source_account} for #{details.amount}")
+ raise InsufficientFundsError, 'Transfer amount too large' if details.amount > 1000
+
+ # Uncomment to expose a bug and cause the Activity to fail
+ x = details.amount / 0
+
+ "OKW-#{details.amount}-#{details.source_account}"
+ end
+end
+```
+
+Save your changes. You've now created a deliberate failure point in your withdraw Activity. This simulates a real-world scenario where external service calls might fail intermittently.
+
+
+
+
+### Step 3: Start Worker & Observe Retry Behavior
+
+Now let's see how Temporal handles this failure. When you start your Worker, it will hit the failing Activity. Instead of the entire Workflow failing permanently, Temporal will retry the failed Activity according to your retry policy.
+
+
+
+
+```bash
+python run_worker.py
+```
+
+**Here's what you'll see:**
+- The `withdraw()` Activity completes successfully
+- The `deposit()` Activity fails and retries automatically
+
+
+
+
+
+
+
+```bash
+go run worker/main.go
+```
+
+**Here's what you'll see:**
+- The `Withdraw()` Activity completes successfully
+- The `Deposit()` Activity fails and retries automatically
+
+
+
+
+
+
+
+Make sure your Workflow is still running in the Web UI, then start your Worker:
+
+```bash
+mvn clean install -Dorg.slf4j.simpleLogger.defaultLogLevel=info 2>/dev/null
+mvn compile exec:java -Dexec.mainClass="moneytransferapp.MoneyTransferWorker" -Dorg.slf4j.simpleLogger.defaultLogLevel=warn
+```
+
+**Here's what you'll see:**
+- The `withdraw()` Activity completes successfully
+- The `deposit()` Activity fails and retries automatically
+
+
+
+
+
+
+
+```bash
+npm run worker
+```
+
+**Here's what you'll see:**
+- The `withdraw()` Activity completes successfully
+- The `deposit()` Activity fails and retries automatically
+
+
+
+
+
+
+
+```bash
+dotnet run --project MoneyTransferWorker/MoneyTransferWorker.csproj
+```
+
+**Here's what you'll see:**
+- The `WithdrawAsync()` Activity completes successfully
+- The `DepositAsync()` Activity fails and retries automatically
+
+
+
+
+
+
+
+```bash
+bundle exec ruby worker.rb
+```
+
+In another terminal, start a new Workflow:
+
+```bash
+bundle exec ruby starter.rb
+```
+
+**Here's what you'll see:**
+- The `Withdraw` Activity fails and retries automatically (due to the divide-by-zero bug)
+- The `Deposit` Activity never runs because the Workflow is stuck on the failing Withdraw
+
+
+
+Check the Web UI - click on your Workflow to see the failure details and retry attempts.
+
+
+
+
+**Key observation:** Your Workflow isn't stuck or terminated. Temporal automatically retries the failed Activity according to your configured retry policy, while maintaining the overall Workflow state. Successfully completed Activities aren't re-executed - only the failed Activity is retried.
+
+### Step 4: Fix the Bug
+
+Here's where Temporal really shines - you can fix bugs in production code while Workflows are still executing. The Workflow state is preserved in Temporal's durable storage, so you can deploy fixes and let the retry mechanism pick up your corrected code.
+
+
+
+
+Go back to `activities.py` and **reverse the comments** - comment out the failing line and uncomment the working line:
+
+**activities.py**
+```python
+@activity.defn
+async def deposit(self, data: PaymentDetails) -> str:
+ reference_id = f"{data.reference_id}-deposit"
+ try:
+ # Uncomment this working line:
+ confirmation = await asyncio.to_thread(
+ self.bank.deposit, data.target_account, data.amount, reference_id
+ )
+
+ # Comment out this failing line:
+ # confirmation = await asyncio.to_thread(
+ # self.bank.deposit_that_fails,
+ # data.target_account,
+ # data.amount,
+ # reference_id,
+ # )
+ return confirmation
+ except InvalidAccountError:
+ raise
+ except Exception:
+ activity.logger.exception("Deposit failed")
+ raise
+```
+
+
+
+
+
+Go back to `activity.go` and **reverse the comments** - comment out the failing line and uncomment the working line:
+
+**activity.go**
+```go
+func Deposit(ctx context.Context, data PaymentDetails) (string, error) {
+ log.Printf("Depositing $%d into account %s.\n\n",
+ data.Amount,
+ data.TargetAccount,
+ )
+
+ referenceID := fmt.Sprintf("%s-deposit", data.ReferenceID)
+ bank := BankingService{"bank-api.example.com"}
+
+ // Comment out this failing line:
+ // confirmation, err := bank.DepositThatFails(data.TargetAccount, data.Amount, referenceID)
+ // Uncomment this working line:
+ confirmation, err := bank.Deposit(data.TargetAccount, data.Amount, referenceID)
+
+ return confirmation, err
+}
+```
+
+
+
+
+
+Go back to `AccountActivityImpl.java` and **change `activityShouldSucceed` back to `true`**:
+
+**src/main/java/moneytransferapp/AccountActivityImpl.java**
+```java
+@Override
+public void deposit(String accountId, String referenceId, int amount) {
+ // Change this back to true to fix the bug:
+ boolean activityShouldSucceed = true;
+
+ if (activityShouldSucceed) {
+ System.out.printf("\nDepositing $%d into account %s.\n[ReferenceId: %s]\n",
+ amount, accountId, referenceId);
+ } else {
+ System.out.println("Deposit failed");
+ throw new RuntimeException("Simulated deposit failure");
+ }
+}
+```
+
+
+
+
+
+Go back to `activities.ts` and **reverse the comments** - comment out the failing line and uncomment the working line:
+
+**src/activities.ts**
+```typescript
+export async function deposit(details: PaymentDetails): Promise {
+ console.log(
+ `Depositing $${details.amount} into account ${details.targetAccount}.\n\n`
+ );
+ const bank = new BankingService('bank2.example.com');
+
+ // Uncomment this working line:
+ return await bank.deposit(details.targetAccount, details.amount, details.referenceId);
+
+ // Comment out this failing line:
+ // return await bank.depositThatFails(details.targetAccount, details.amount, details.referenceId);
+}
+```
+
+
+
+
+
+Go back to `Activities.cs` and **reverse the comments** - comment out the failing line and uncomment the working block:
+
+**MoneyTransferWorker/Activities.cs**
+```csharp
+[Activity]
+public static async Task DepositAsync(PaymentDetails details)
+{
+ var bankService = new BankingService("bank2.example.com");
+ Console.WriteLine($"Depositing ${details.Amount} into account {details.TargetAccount}.");
+
+ // Comment out this failing line:
+ // return await bankService.DepositThatFailsAsync(details.TargetAccount, details.Amount, details.ReferenceId);
+
+ // Uncomment this working block:
+ try
+ {
+ return await bankService.DepositAsync(details.TargetAccount, details.Amount, details.ReferenceId);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationFailureException("Deposit failed", ex);
+ }
+}
+```
+
+
+
+
+
+Go back to `activities.rb` and **comment out the failing line**:
+
+**activities.rb**
+```ruby
+class Withdraw < Temporalio::Activity::Definition
+ def execute(details)
+ puts("Doing a withdrawal from #{details.source_account} for #{details.amount}")
+ raise InsufficientFundsError, 'Transfer amount too large' if details.amount > 1000
+
+ # Comment out this failing line:
+ # x = details.amount / 0
+
+ "OKW-#{details.amount}-#{details.source_account}"
+ end
+end
+```
+
+
+
+
+Save your changes. You've now restored the working implementation. The key insight here is that you can deploy fixes to Activities while Workflows are still executing - Temporal will pick up your changes on the next retry attempt.
+
+### Step 5: Restart Worker
+
+To apply your fix, you need to restart the Worker process so it picks up the code changes. Since the Workflow Execution state is stored in Temporal's servers (not in your Worker process), restarting the Worker won't affect the running Workflow.
+
+
+
+
+```bash
+# Stop the current Worker
+Ctrl+C
+
+# Start it again with the fix
+python run_worker.py
+```
+
+**On the next retry attempt,** your fixed `deposit()` Activity will succeed, and you'll see the completed transaction in Terminal 3:
+
+```
+Transfer complete.
+Withdraw: {'amount': 250, 'receiver': '43-812', 'reference_id': '1f35f7c6-4376-4fb8-881a-569dfd64d472', 'sender': '85-150'}
+Deposit: {'amount': 250, 'receiver': '43-812', 'reference_id': '1f35f7c6-4376-4fb8-881a-569dfd64d472', 'sender': '85-150'}
+```
+
+
+
+
+
+```bash
+# Stop the current Worker
+Ctrl+C
+
+# Start it again with the fix
+go run worker/main.go
+```
+
+**On the next retry attempt,** your fixed `Deposit()` Activity will succeed, and you'll see the completed transaction in your starter terminal:
+
+```
+Transfer complete (transaction IDs: W1779185060, D1779185060)
+```
+
+
+
+
+
+```bash
+# Stop the current Worker
+Ctrl+C
+
+# Start it again with the fix
+mvn clean install -Dorg.slf4j.simpleLogger.defaultLogLevel=info 2>/dev/null
+mvn compile exec:java -Dexec.mainClass="moneytransferapp.MoneyTransferWorker" -Dorg.slf4j.simpleLogger.defaultLogLevel=warn
+```
+
+**On the next retry attempt,** your fixed `deposit()` Activity will succeed:
+
+```
+Depositing $32 into account 872878204.
+[ReferenceId: d3d9bcf0-a897-4326]
+[d3d9bcf0-a897-4326] Transaction succeeded.
+```
+
+
+
+
+
+```bash
+# Stop the current Worker
+Ctrl+C
+
+# Start it again with the fix
+npm run worker
+```
+
+**On the next retry attempt,** your fixed `deposit()` Activity will succeed, and you'll see the completed transaction in your client terminal:
+
+```
+Transfer complete (transaction IDs: W3436600150, D9270097234)
+```
+
+
+
+
+
+```bash
+# Stop the current Worker
+Ctrl+C
+
+# Start it again with the fix
+dotnet run --project MoneyTransferWorker/MoneyTransferWorker.csproj
+```
+
+**On the next retry attempt,** your fixed `DepositAsync()` Activity will succeed, and you'll see the completed transaction in your client terminal:
+
+```
+Workflow result: Transfer complete (transaction IDs: W-caa90e06-3a48-406d-86ff-e3e958a280f8, D-1910468b-5951-4f1d-ab51-75da5bba230b)
+```
+
+
+
+
+
+```bash
+# Stop the current Worker
+Ctrl+C
+
+# Start it again with the fix
+bundle exec ruby worker.rb
+```
+
+**On the next retry attempt,** your fixed `Withdraw` Activity will succeed, and you'll see the Workflow complete successfully.
+
+
+
+
+Check the Web UI - your Workflow shows as completed. You've just demonstrated Temporal's key differentiator: the ability to fix production bugs in running applications without losing transaction state or progress. This is possible because Temporal stores execution state separately from your application code.
+
+**Mission Accomplished.** You have just fixed a bug in a running application without losing the state of the Workflow or restarting the transaction.
+
+
+
+
+
+
+:::tip Advanced Challenge
+
+Try this advanced scenario of compensating transactions.
+
+
+1. **Modify the retry policy** in the workflow file to only retry 1 time
+2. **Force the deposit to fail permanently**
+3. **Watch the automatic refund** execute
+
+:::
+
+
+## Knowledge Check
+
+Test your understanding of what you just experienced:
+
+
+Q: What are four of Temporal's value propositions that you learned about in this tutorial?
+
+**Answer**:
+1. Temporal automatically maintains the state of your Workflow, despite crashes or even outages of the Temporal Service itself.
+2. Temporal's built-in support for retries and timeouts enables your code to overcome transient and intermittent failures.
+3. Temporal provides full visibility in the state of the Workflow Execution and its Web UI offers a convenient way to see the details of both current and past executions.
+4. Temporal makes it possible to fix a bug in a Workflow Execution that you've already started. After updating the code and restarting the Worker, the failing Activity is retried using the code containing the bug fix, completes successfully, and execution continues with what comes next.
+
+
+
+Q: Why do we use a shared constant for the Task Queue name?
+
+**Answer**: Because the Task Queue name is specified in two different parts of the code (the first starts the Workflow and the second configures the Worker). If their values differ, the Worker and Temporal Service would not share the same Task Queue, and the Workflow Execution would not progress.
+
+
+
+
+
+Q: What do you have to do if you make changes to Activity code for a Workflow that is running?
+
+**Answer**: Restart the Worker.
+
+
+
+
+## Continue Your Learning
+
+