> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outerproduct.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Runs, calls, and attempts

> Understand how OuterProduct organizes and executes remote functions.

A Run is a remote execution of your code on OuterProduct. It starts with a single function Call and can include additional child function Calls launched by your code as it runs.

Each Call combines a function and its arguments with the software dependencies, hardware, environment, and durability settings required to run it (see [Images](https://docs.outerproduct.com/images.md), [Hardware](https://docs.outerproduct.com/hardware.md), [Environment variables](https://docs.outerproduct.com/env-vars.md), [Volumes](https://docs.outerproduct.com/volumes.md), and [Retries and durability](https://docs.outerproduct.com/retries-and-durability.md) for more details).

OuterProduct tracks execution at three levels:

* **Run:** The complete execution, including the initial function Call and any Calls it launches.
* **Call:** One function invocation submitted through the Python API with its arguments and execution settings. Every Run starts with a root Call.
* **Attempt:** One try at executing a Call. Retrying a Call creates another Attempt for that Call within the same Run.

A Run can contain many Calls, and each Call can have multiple Attempts. The OuterProduct UI shows this structure alongside execution status and logs.

## Running a function

To create a Run in OuterProduct, use `client.run()` to create its first Call. This Call becomes the root of the new Run. Any Calls created by the function as it executes become child Calls within the same Run. The function must take an OuterProduct `client` as its first parameter. When calling `client.run()`, specify the function, its arguments, and its compute requirements.

```python theme={null}
import outerproduct as op


def sum_batch(client, values: list[int]) -> int:
    return sum(values)


if __name__ == "__main__":
    client = op.init()
    image: op.Image = op.Image.from_registry("python:3.13-slim")

    run: op.Run[int] | op.Call[int] = client.run(
        fn=sum_batch,
        name="Sum batch",
        args=([1, 2, 3],),
        image=image,
    )

    print(run.wait())  # 6
```

OuterProduct supplies the function’s `client` argument automatically. The remaining positional arguments can be supplied through the `args` parameter, and named arguments can be supplied through `kwargs`.

`client.run()` returns a handle to the Run after submission, without waiting for execution to complete. Calling `.wait()` blocks until the Run completes and returns the function’s result.

## Run lifecycle

`run.status` is a run status value stored in the handle. It is normally `RUNNING`, `CANCELLING`, `COMPLETED`, `FAILED`, or `CANCELLED`. Call `run.refresh()` to fetch the latest state. It returns a new handle, so assign the result if you need its updated status.

```python theme={null}
run_id: str = run.id
later: op.Run[object] = client.get_run(run_id)
later = later.refresh()
print(later.status)
```

`client.list_runs()` lists Runs available to you. Save `run.id` if you want to retrieve a particular Run later with `client.get_run()`.

`run.wait(timeout=30)` waits up to 30 seconds for the result. If the Run has not finished by then, `wait()` raises `TimeoutError` and stops waiting. The Run keeps running on OuterProduct. Omit `timeout` to wait indefinitely, or set `timeout=0` to check once. If the Run fails or is cancelled, `wait()` raises `op.RunFailed`.

`run.cancel()` requests cancellation and returns an updated handle. The status can be `CANCELLING` while execution stops. You can also cancel a Run by ID with `client.cancel_run(run_id)` and through the OuterProduct UI via the cancel button.

## Inspecting Calls and Attempts

`await run.calls()` returns every Call in the Run, including the root Call and nested Calls. `await call.children()` returns only the Calls directly launched by that Call. Each Call has a status, an error message when available, and an `attempt_count`.

`await call.attempts()` returns its Attempts. Each Attempt has a number, status, and error message when available:

```python theme={null}
import asyncio


async def inspect_run(run: op.Run[object]) -> None:
    for call in await run.calls():
        print(call.name, call.status)
        for attempt in await call.attempts():
            print(attempt.number, attempt.status, attempt.error_message)


asyncio.run(inspect_run(client.get_run(run.id)))
```

Use `await call.latest_attempt()` to fetch only the newest Attempt; it returns `None` if the Call has no Attempts. To retrieve a saved resource by its canonical name, use `await client.get_call(name)` or `await client.get_attempt(name)`. The client also provides `list_calls(run)` and `list_attempts(call)`. These inspection methods are async only; see [Async usage](https://docs.outerproduct.com/async-usage.md).

## Arguments and results

When creating a Call, pass `client.run()` the function and its arguments, along with the settings that control its execution.

OuterProduct uploads and mounts the function’s Python file, or its entire top-level package when it belongs to one, then imports the function by name. Submitted functions should be defined at the top level of a Python file. Nested functions and lambdas are not supported. Because each Attempt imports the file, code that submits Calls goes under `if __name__ == "__main__":` (`client.run()` raises `RuntimeError` if the import calls it). Other local files can be included through the Image’s `add_local_file()` and `add_local_dir()` methods. Third-party dependencies should be installed in the Image.

Arguments and return values are serialized using `cloudpickle` and can be any Python objects it supports. Return values are reconstructed as Python objects when `.wait()` is called. Any libraries needed for this should also be installed in the calling program.

For example, a parent Call receiving a PyTorch object should have PyTorch installed in its Image. A local script receiving that same object should have PyTorch installed locally.

> [!NOTE]
>
> Large arrays, datasets, model weights, and other large inputs or outputs are often better stored in a [Volume](https://docs.outerproduct.com/volumes.md) and managed through volume mounts (or an object store more generally).

Each argument to `client.run()` controls one part of the execution:

```python theme={null}
import outerproduct as op


def my_application(_client, input_path: str) -> str:
    # ...
    return f"Processed {input_path}"


if __name__ == "__main__":
    client = op.init()

    run: op.Run[str] | op.Call[str] = client.run(
        fn=my_application,
        args=("/data/input.json",),
        image=op.Image.from_registry("python:3.12-slim"),
        cpu=4,
        memory_bytes=16 * 1024**3,  # 16 GiB
        gpu_type="L4",
        env_vars={"ENVIRONMENT": "production"},
        volumes={"/data": data_volume},
        retry_strategy=op.RetryStrategy(max_attempts=4),
	   timeout=3600,
        name="Process input data",
    )

    result: str = run.wait()
```

These arguments are explored in more details in dedicated sections of the docs:

* **Function (`fn`):** The Python function to execute. It may be synchronous or asynchronous and must accept an OuterProduct client as its first argument.
* **Arguments (`args` and `kwargs`):** The values passed to the function after its client argument. Arguments must be serializable.
* **Image (`image`):** The container environment in which the function runs. The image defines the operating system, Python runtime, and installed dependencies (see [Images](https://docs.outerproduct.com/images.md)).
* **Hardware (`cpu`, `memory_bytes`, `gpu`, and `gpu_type`):** The compute resources assigned to the function. You can choose the number of CPUs, available memory, GPU count, and GPU type (see [Hardware](https://docs.outerproduct.com/hardware.md)).
* **Environment variables (`env_vars`):** Configuration injected into the container. Values can be plain strings or securely stored OuterProduct secrets (see [Environment variables](https://docs.outerproduct.com/env-vars.md)).
* **Volumes (`volumes`):** Persistent storage mounted inside the container. Each entry maps a container path, such as `/data`, to an OuterProduct Volume (see  [Volumes](https://docs.outerproduct.com/volumes.md)).
* **Retry strategy (**`retry_strategy`**):** Controls how OuterProduct responds when an attempt fails. By default, the Python SDK allows a Call to be attempted up to three times; pass `RetryStrategy(max_attempts=1)` to disable retries (see  [Retries and durability](https://docs.outerproduct.com/retries-and-durability.md)).
* **Timeout (`timeout`):** How many seconds each Attempt may run, up to 24 hours, the default. Calls that mount volumes should set it to an hour or less (see [Volumes](https://docs.outerproduct.com/volumes.md)).
* **Name (**`name`**):** Optional display label for the Run, or for a child Call when `client.run()` is called inside a Run. If omitted, the label defaults to the name of the function you pass to `client.run()`.

> [!NOTE]
>
> We recommend that you or your agent name Runs with the `name` argument to make them easier to identify and track in the OuterProduct UI. The `name` argument only changes the label shown in the UI and has no impact on the Run’s or child Call’s resource identifier.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.