# Defining Workflow Tasks


> *[Render Workflows](workflows) are in limited early access.*
>
> During the early access period, the Workflows API and SDKs might introduce breaking changes.

After you [create your first workflow](workflows-tutorial), you can start defining your own tasks. This article describes supported syntax and configuration options.

## Basic example

> *The Workflows SDK is currently available only for [Python](workflows-sdk-python).*
>
> TypeScript support is nearing release ([see GitHub](https://github.com/render-oss/sdk/tree/main/typescript)).
>
> SDKs for additional languages are planned for future releases.

**Python**

Define a task in your workflow service by applying the `@task` decorator to any function, like so:

```python:main.py
from render_sdk.workflows import task, start

@task #highlight-line
def calculate_square(a: int) -> int:
  return a * a

if __name__ == "__main__":
  start()
```

This defines a basic task named `calculate_square` that takes a single integer argument and returns its square.

For all supported `@task` options, see the [Python SDK reference](workflows-sdk-python#the-task-decorator).

## Organizing tasks

You can define your workflow's tasks across any number of files in your project repo:

<columns>
<column>

```python:main.py
from render_sdk.workflows import (
  task,
  start,
)
import math_tasks # Import all tasks

@task
def capitalize(s: str) -> str:
  return s.upper()

if __name__ == "__main__":
  start() # SDK entry point
```

</column>

<column>

```python:math_tasks.py
from render_sdk.workflows import task

@task
def calculate_square(a: int) -> int:
  return a * a

@task
def add(a: int, b: int) -> int:
  return a + b
```

</column>
</columns>

In the above example, task definitions are distributed across two files: `main.py` and `math_tasks.py`.

For tasks to register successfully, your workflow's entry point (commonly `main.py`) must import your other files that contain task definitions.

## Task arguments

**A task's function can define any number of arguments.** This example task takes three arguments of different types:

```python
@task
def my_task(arg1: int, arg2: str, arg3: bool) -> int:
  # ...
```

**Task arguments are positional.** Whenever you [run a task](workflows-running), you provide its arguments in a JSON array in the same order as they appear in the task's function signature:

```python
started_run = await client.workflows.run_task(
  task_identifier="my-workflow/my-task",
  input_data=[1, "hello", True]
)
```

**Argument and return types must be JSON-serializable.** Your applications provide task arguments in a JSON array via the Render API, and a task's result is also returned as JSON.

**All task arguments are required.** If you attempt to run a task with missing arguments (or _too many_ arguments), the task will fail. Argument values _can_ be null, as long as your task logic supports this.

## Retry logic

Your tasks can automatically **retry** if a run fails. A task run is considered to have failed if its function raises an exception instead of returning a value.

Retries are useful for tasks that might be affected by temporary failures, such as network errors or timeouts.

### Default retry behavior

By default, tasks use the following retry logic:

- Retry up to 3 times (i.e., 4 total attempts)
- Wait 1 second before attempting the first retry
- Double the wait time after each retry (i.e., one second, two seconds, four seconds)

### Customizing retries

You can customize retry behavior on a per-task basis:

**Python**

Provide retry settings to the `@task` decorator with the following syntax:

```python{1,5-11}
from render_sdk.workflows import task, Options, Retry
import random

@task(
  options=Options(
    retry=Retry(
      max_retries=3, # Retry up to 3 times (i.e., 4 total attempts)
      wait_duration_ms=1000, # Set a base retry delay of 1 second
      factor=1.5 # Increase delay by 50% after each retry (exp. backoff)
    )
  )
)
def flip_coin() -> str:
  if random.random() < 0.5:
    raise Exception("Flipped tails! Retrying.")
  return "Flipped heads!"
```

This contrived example defines a task named `flip_coin` that raises an exception when it "flips tails", causing the run to fail and retry according to its settings.

## Running subtasks

A task can straightforwardly run other tasks that are defined in the same workflow. These **subtasks** each run in their own instance, just like their parent.

<img src="../assets/images/docs/workflow-diagram.svg" width="700" alt="Workflows overview" />

> **When should I run a subtask?**
>
> Subtasks are most helpful when different parts of a larger job benefit from long-running, independent compute.
>
> For simple jobs (such as the very basic example below), it's more efficient to perform the entirety of your logic in a single task.

**Python**

The simple `sum_squares` task below runs two `calculate_square` subtasks:

```python:math_tasks.py
from render_sdk.workflows import task
import asyncio