# Start a Workflow with static summary and details
handle = await client.start_workflow(
    YourWorkflow.run,
    "workflow input",
    id="your-workflow-id",
    task_queue="your-task-queue",
    static_summary="Order processing for customer #12345",
    static_details="Processing premium order with expedited shipping"
)
```

`static_summary` is a single-line description that appears in the Workflow list view, limited to 200 bytes.
`static_details` can be multi-line and provides more comprehensive information that appears in the Workflow details view, with a larger limit of 20K bytes.

The input format is standard Markdown excluding images, HTML, and scripts.

You can also use the `execute_workflow` method with the same parameters:

```python
result = await client.execute_workflow(
    YourWorkflow.run,
    "workflow input",
    id="your-workflow-id",
    task_queue="your-task-queue",
    static_summary="Order processing for customer #12345",
    static_details="Processing premium order with expedited shipping"
)
```

### Inside the Workflow

Within a Workflow, you can get and set the _current Workflow details_. 
Unlike static summary/details set at Workflow start, this value can be updated throughout the life of the Workflow. 
Current Workflow details also takes Markdown format (excluding images, HTML, and scripts) and can span multiple lines.

```python
@workflow.defn
class YourWorkflow:
    @workflow.run
    async def run(self, input: str) -> str:
        # Get the current details
        current_details = workflow.get_current_details()
        print(f"Current details: {current_details}")
        
        # Set/update the current details
        workflow.set_current_details("Updated workflow details with new status")
        
        return "Workflow completed"
```

### Adding Summary to Activities and Timers

You can attach a metadata parameter `summary` to Activities when starting them from within a Workflow:

```python
@workflow.defn
class YourWorkflow:
    @workflow.run
    async def run(self, input: str) -> str:
        # Start an activity with a summary
        result = await workflow.execute_activity(
            your_activity,
            input,
            start_to_close_timeout=timedelta(seconds=10),
            summary="Processing user data"
        )
        return result
```

Similarly, you can attach a `summary` to Timers within a Workflow:

```python
@workflow.defn
class YourWorkflow:
    @workflow.run
    async def run(self, input: str) -> str:
        # Create a timer with a summary
        await workflow.sleep(timedelta(minutes=5), summary="Waiting for payment confirmation")
        return "Timer completed"
```
The input format for `summary` is a string, and limited to 200 bytes.

## Viewing Summary and Details in the UI

Once you've added summaries and details to your Workflows, Activities, and Timers, you can view this enriched information in the Temporal Web UI. 
Navigate to your Workflow's details page to see the metadata displayed in two key locations:

### Workflow Overview Section

At the top of the Workflow details page, you'll find the Workflow-level metadata:

- **Summary & Details** - Displays the static summary and static details set when starting the Workflow
- **Current Details** - Displays the dynamic details that can be updated during Workflow execution

All Workflow details support standard Markdown formatting (excluding images, HTML, and scripts), allowing you to create rich, structured information displays.

### Event History

Individual events in the Workflow's Event History display their associated summaries when available.

Workflow, Activity and Timer summaries appear in purple text next to their corresponding events, providing immediate context without requiring you to expand the Event details. 
When you do expand an Event, the summary is also prominently displayed in the detailed view.

---

## Error handling - Python SDK

Temporal automatically handles many types of failures through retries and Durable Execution.
This page shows you how to build on these capabilities to create robust error handling for your applications.

**Key concepts:**

Not all failures should be handled the same way.
**Transient failures** (like brief network hiccups) resolve on their own and should be retried immediately.
**Intermittent failures** (like rate limiting) need increasing delays between retries.
**Permanent failures** (like invalid input) won't resolve through retries and need different data or code changes.

Temporal distinguishes between **Workflow Task failures** (bugs that can be fixed with redeployment) and **Workflow Execution failures** (business logic failures that should stop the Workflow).
Task failures retry automatically so you can fix and redeploy without losing state.
Execution failures require you to explicitly raise an `ApplicationError`.

This page shows how to:

- [Make Activities idempotent](#make-activities-idempotent)
- [Raise exceptions from Activities](#raise-exceptions-from-activities)
- [Raise exceptions from Workflows](#raise-exceptions-from-workflows)
- [Handle exceptions in Workflows](#handle-exceptions-in-workflows)
- [Configure custom Retry Policies](#configure-custom-retry-policies)
- [Mark specific errors as non-retryable](#mark-errors-as-non-retryable)
- [Specify non-retryable error types in Retry Policies](#specify-non-retryable-error-types)
- [Implement rollback logic with the Saga pattern](#implement-saga-pattern)
- [Understand Temporal's failure types](#understand-failure-types)

## Make Activities idempotent {#make-activities-idempotent}

**How to make Activities idempotent using the Temporal Python SDK**

Because Activities may be retried due to failures, it's strongly recommended to make them idempotent.
An idempotent operation produces the same result whether executed once or multiple times.

Activities follow an at-least-once execution model.
If a Worker executes an Activity successfully but crashes before notifying the Temporal Service, the Activity will be retried.
Without idempotence, this could cause duplicate charges in payment processing or create duplicate resources in infrastructure provisioning.

### Use idempotency keys

Most external services support idempotency keys—unique identifiers that prevent duplicate operations.
When the service receives a request with a key it has already processed, it returns the original result instead of performing the operation again.

Create an idempotency key by combining the Workflow Run ID and Activity ID:

```python
from temporalio import activity

@activity.defn
async def process_payment(amount: float, account: str):
    info = activity.info()
    idempotency_key = f"{info.workflow_run_id}-{info.activity_id}"

    # Pass idempotency_key to your payment service
    result = await payment_service.charge(
        amount=amount,
        account=account,
        idempotency_key=idempotency_key
    )
    return result
```

This value remains constant across Activity retries but is unique among all Workflow Executions.

### Design Activities to be atomic

Activities are atomic—they either complete successfully or not.
If an Activity performs multiple steps and the last step fails, the entire Activity is retried.

Consider this Activity:
1. Look up data in database
2. Call microservice with the data
3. Write result to filesystem

If step 3 fails, all three steps execute again on retry.
You might split this into three separate Activities so only the failed step retries, but balance this against having a larger Event History with more Activity Executions.

## Raise exceptions from Activities {#raise-exceptions-from-activities}

**How to raise exceptions from Activities using the Temporal Python SDK**

Use `ApplicationError` to communicate application-specific failures from Activities.
Temporal converts any Python exception raised in an Activity to an `ApplicationError`, but raising it explicitly gives you more control.

```python
from temporalio import activity
from temporalio.exceptions import ApplicationError

@activity.defn
async def validate_charge(credit_card_number: str, amount: float):
    if not is_valid_card(credit_card_number):
        raise ApplicationError(
            f"Invalid credit card number: {credit_card_number}",
            type="InvalidCreditCard",
        )

    if amount <= 0:
        raise ApplicationError(
            f"Amount must be positive, got {amount}",
            type="InvalidAmount",
        )

    return True
```

When raising an `ApplicationError`:
- Provide a descriptive `message`
- Optionally provide a `type` string to categorize the failure
- The error appears in the Event History as an `ActivityTaskFailed` event

When an Activity fails, Temporal wraps the exception in an `ActivityError` before surfacing it to the Workflow.
The `ActivityError` provides context including:
- Activity type that failed
- Number of retry attempts
- Original cause (the `ApplicationError` you raised, or `TimeoutError`, `CancelledError`, etc.)

## Raise exceptions from Workflows {#raise-exceptions-from-workflows}

**How to raise exceptions from Workflows using the Temporal Python SDK**

The behavior depends on what exception you raise:

### Fail a Workflow Execution

To deliberately fail a Workflow Execution, raise an `ApplicationError`:

```python
from temporalio import workflow
from temporalio.exceptions import ApplicationError

@workflow.defn
class PizzaDeliveryWorkflow:
    @workflow.run
    async def run(self, order):
        distance = await workflow.execute_activity(
            calculate_distance,
            order.address,
            start_to_close_timeout=timedelta(seconds=10)
        )

        if order.is_delivery and distance.kilometers > 25:
            workflow.logger.error("Customer outside service area")
            raise ApplicationError(
                "Customer lives outside the service area",
                type="CustomerOutsideServiceArea"
            )

        # Continue with order...
```

This puts the Workflow Execution in "Failed" state with no automatic retries.
Use this for permanent failures where retrying won't help—like the customer being too far away.

### Trigger a Workflow Task retry

Raising any other Python exception (like `ValueError` or `TypeError`) causes a Workflow Task failure, which retries automatically:

```python